@wcag-checkr/ci 1.0.0-rc.13

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 (50) hide show
  1. package/README.md +135 -0
  2. package/dist/assets/ErrorBoundary-BPz4qckm.js +524 -0
  3. package/dist/assets/_commonjsHelpers-Cpj98o6Y.js +1 -0
  4. package/dist/assets/ai-usage-log-DFkwAfmW.js +1 -0
  5. package/dist/assets/content-script.ts-D7yXcBUr.js +181 -0
  6. package/dist/assets/content-script.ts-loader-Cn8Y9Xod.js +13 -0
  7. package/dist/assets/crash-reporter-wxu43qbG.js +4 -0
  8. package/dist/assets/devtools-panel-D2fL4guz.js +1 -0
  9. package/dist/assets/devtools.html-DQBohI9U.js +1 -0
  10. package/dist/assets/diff-D4sCAdXf.js +1 -0
  11. package/dist/assets/forensic-log-B3iX62mE.js +129 -0
  12. package/dist/assets/main-CqDdt0Iq.js +6 -0
  13. package/dist/assets/main-DyQfCbPM.js +1 -0
  14. package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js +1 -0
  15. package/dist/assets/options.html-jfjpxZBp.js +1 -0
  16. package/dist/assets/preload-helper-D7HrI6pR.js +1 -0
  17. package/dist/assets/reflow-analyzer-DNgBX8N_.js +1 -0
  18. package/dist/assets/service-worker.ts-DaHvU8nE.js +715 -0
  19. package/dist/assets/side-panel.html-DW1tssqQ.js +1 -0
  20. package/dist/assets/site-report-renderer-JH44v2hK.js +147 -0
  21. package/dist/assets/state-DnzwwNxZ.js +1 -0
  22. package/dist/assets/styles-DP9v_aMy.css +1 -0
  23. package/dist/assets/styles-kHMb1Lda.js +84 -0
  24. package/dist/devtools/devtools.html +11 -0
  25. package/dist/devtools/panel.html +20 -0
  26. package/dist/fonts/mona-sans-variable.woff2 +0 -0
  27. package/dist/icons/icon-128.png +0 -0
  28. package/dist/icons/icon-16.png +0 -0
  29. package/dist/icons/icon-32.png +0 -0
  30. package/dist/icons/icon-48.png +0 -0
  31. package/dist/manifest.json +70 -0
  32. package/dist/options/options.html +19 -0
  33. package/dist/service-worker-loader.js +1 -0
  34. package/dist/side-panel/App.tsx +174 -0
  35. package/dist/side-panel/README.md +57 -0
  36. package/dist/side-panel/audit-launcher.test.ts +56 -0
  37. package/dist/side-panel/audit-launcher.ts +65 -0
  38. package/dist/side-panel/format-component-id.test.ts +89 -0
  39. package/dist/side-panel/format-component-id.ts +40 -0
  40. package/dist/side-panel/github-issue.test.ts +102 -0
  41. package/dist/side-panel/github-issue.ts +66 -0
  42. package/dist/side-panel/jira-issue.ts +64 -0
  43. package/dist/side-panel/main.tsx +19 -0
  44. package/dist/side-panel/side-panel.html +21 -0
  45. package/dist/side-panel/store.ts +264 -0
  46. package/dist/side-panel/styles.css +16 -0
  47. package/dist/side-panel/wire-messaging.test.ts +202 -0
  48. package/dist/side-panel/wire-messaging.ts +285 -0
  49. package/package.json +39 -0
  50. package/wcagcheckr-ci.mjs +559 -0
@@ -0,0 +1,13 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const injectTime = performance.now();
5
+ (async () => {
6
+ const { onExecute } = await import(
7
+ /* @vite-ignore */
8
+ chrome.runtime.getURL("assets/content-script.ts-D7yXcBUr.js")
9
+ );
10
+ onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });
11
+ })().catch(console.error);
12
+
13
+ })();
@@ -0,0 +1,4 @@
1
+ const u=[];function M(e){u.push(e),u.length>50&&u.shift()}function S(){return[...u]}const l={debug:0,info:1,warn:2,error:3,silent:4};let $="info";function y(e){return{debug:(n,...r)=>f("debug",e,n,r),info:(n,...r)=>f("info",e,n,r),warn:(n,...r)=>f("warn",e,n,r),error:(n,...r)=>f("error",e,n,r)}}function f(e,n,r,t){if(M(b(e,n,r,t)),l[e]<l[$])return;const o=`[${n}]`,s=e==="error"?console.error:e==="warn"?console.warn:console.log;t.length>0?s(o,r,...t):s(o,r)}function b(e,n,r,t){const o=new Date().toISOString(),s=t.length>0?` ${t.map(O).join(" ")}`:"";return`${o} ${e.toUpperCase()} [${n}] ${r}${s}`}function O(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}const _=y("messaging");function j(e){return chrome.runtime.sendMessage(e)}function q(e,n,r){const t=r!==void 0?{frameId:r}:void 0;return chrome.tabs.sendMessage(e,n,t)}async function C(e){const n=await chrome.runtime.sendMessage(e);if(!d(n))throw new Error(`messaging.request: response was not a Message (sent ${e.type}, got ${typeof n})`);return n}const h=45e3;async function U(e,n,r){const t=r!==void 0?{frameId:r}:void 0,o=await Promise.race([chrome.tabs.sendMessage(e,n,t),new Promise((s,a)=>setTimeout(()=>a(new Error(`messaging.requestFromTab: ${n.type} timed out after ${h/1e3}s`)),h))]);if(!d(o))throw new Error(`messaging.requestFromTab: response was not a Message (sent ${n.type}, got ${typeof o})`);return o}function x(e,n){const r=(t,o,s)=>{if(!d(t)||t.type!==e)return!1;let a;try{a=n(t,o)}catch(c){return p(e,c),s(void 0),!1}return a instanceof Promise?(a.then(c=>s(c??void 0)).catch(c=>{p(e,c),s(void 0)}),!0):(s(a??void 0),!1)};return chrome.runtime.onMessage.addListener(r),()=>chrome.runtime.onMessage.removeListener(r)}function D(e){chrome.runtime.sendMessage(e).catch(()=>{})}function d(e){return typeof e=="object"&&e!==null&&typeof e.type=="string"}function p(e,n){_.error(`handler for ${e} failed`,n)}const I=/https?:\/\/[^\s)]+/g,N=/[\w.+-]+@[\w-]+\.[\w.-]+/g,R=/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;function k(e){return e.replace(I,"[URL]").replace(N,"[EMAIL]").replace(R,"[TOKEN]")}function L(e){return e.map(k)}const A=y("crash-reporter"),F="https://api.wcagcheckr.com/v1/products/wcagcheckr/crash",g="crashReporting:enabled",w=10;let T="unknown",m=!1;const i=new Set;function G(e){m||(m=!0,T=e,typeof window<"u"&&(window.addEventListener("error",n=>{E(n.error??new Error(n.message))}),window.addEventListener("unhandledrejection",n=>{const r=n.reason,t=r instanceof Error?r:new Error(String(r));E(t)})))}async function E(e){if(!await P())return;const n=v(e);if(i.has(n))return;if(i.add(n),i.size>w){const t=[...i].slice(-w);i.clear(),t.forEach(o=>i.add(o))}const r={productSlug:"wcagcheckr",context:T,fingerprint:n,message:e.message,stack:X(e.stack??""),extensionVersion:chrome.runtime.getManifest().version,occurredAt:new Date().toISOString(),logTail:L(S())};try{await fetch(F,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)})}catch(t){A.warn("crash report failed to send",t)}}async function P(){try{return(await chrome.storage.local.get(g))[g]===!0}catch{return!1}}async function K(e){await chrome.storage.local.set({[g]:e})}function v(e){var r;const n=((r=(e.stack??"").split(`
2
+ `)[1])==null?void 0:r.trim())??"";return`${e.name}:${e.message}::${n}`.slice(0,200)}function X(e){return L(e.split(`
3
+ `)).join(`
4
+ `)}export{g as O,U as a,D as b,y as c,E as d,C as e,j as f,S as g,K as h,G as i,x as o,L as r,q as s};
@@ -0,0 +1 @@
1
+ import"./modulepreload-polyfill-B5Qt9EMX.js";import{c as t,j as o,R as e}from"./styles-kHMb1Lda.js";import{E as s,A as i}from"./ErrorBoundary-BPz4qckm.js";import{i as m}from"./crash-reporter-wxu43qbG.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./state-DnzwwNxZ.js";import"./preload-helper-D7HrI6pR.js";import"./forensic-log-B3iX62mE.js";m("devtools-panel");const r=document.getElementById("root");if(!r)throw new Error("devtools panel: #root not found");t(r).render(o.jsx(e.StrictMode,{children:o.jsx(s,{children:o.jsx(i,{})})}));
@@ -0,0 +1 @@
1
+ import"./modulepreload-polyfill-B5Qt9EMX.js";chrome.devtools.panels.create("A11y","icons/icon-32.png","devtools/panel.html",()=>{});
@@ -0,0 +1 @@
1
+ var l=Object.defineProperty;var h=(t,e,n)=>e in t?l(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var m=(t,e,n)=>h(t,typeof e!="symbol"?e+"":e,n);function A(t,e,n=!1,r){return{code:t,message:e,recoverable:n,details:r}}class w extends Error{constructor(n){super(n.message);m(this,"payload");this.name="TypedError",this.payload=n}}function K(t){return t instanceof w}async function x(t){const e=i(t.currentState),n=[t.target.tagName,t.target.role??"",t.target.accessibleName??"",t.target.textSnippet??"",t.target.attrId??"",t.target.attrTestid??""].join("|");return E(`${t.ruleId}|${t.componentId}|${e}|${n}`)}function j(t,e,n,r){const c=new Map(t.map(s=>[s.matchKey,s])),S=new Map(e.map(s=>[s.matchKey,s])),a=e.filter(s=>!c.has(s.matchKey)),u=e.filter(s=>c.has(s.matchKey)),f=t.filter(s=>!S.has(s.matchKey));let p,y;if(r){if(r.currentAnnouncements){const s=new Set((r.baselineAnnouncements??[]).map(g));p=r.currentAnnouncements.filter(o=>!s.has(g(o)))}if(r.currentFocusEvents){const s=new Set((r.baselineFocusEvents??[]).map(d));y=r.currentFocusEvents.filter(o=>!s.has(d(o)))}}return{new:a,persistent:u,fixed:f,newCount:a.length,persistentCount:u.length,fixedCount:f.length,baselineSnapshotMeta:n,comparedAt:new Date().toISOString(),newAnnouncements:p,newFocusEvents:y}}function g(t){return`${t.selector}::${t.text}::${t.politeness}`}function d(t){return`${t.toSelector}::${t.isFocusReset}`}function i(t){if(t===null||typeof t!="object")return JSON.stringify(t);if(Array.isArray(t))return"["+t.map(i).join(",")+"]";const e=t;return"{"+Object.keys(e).sort().map(c=>`${JSON.stringify(c)}:${i(e[c])}`).join(",")+"}"}async function E(t){const e=new TextEncoder().encode(t),n=await crypto.subtle.digest("SHA-1",e);return Array.from(new Uint8Array(n)).map(r=>r.toString(16).padStart(2,"0")).join("")}export{w as T,i as a,x as c,j as d,K as i,A as m};
@@ -0,0 +1,129 @@
1
+ import{o as x,b as D,c as oe}from"./crash-reporter-wxu43qbG.js";import{c as B,T as re,b as X}from"./state-DnzwwNxZ.js";async function ct(){const e=await chrome.tabs.query({currentWindow:!0}),t=e.find(n=>n.active);if(t!=null&&t.id&&z(t.url))return t.id;const a=e.find(n=>n.id&&z(n.url));return(a==null?void 0:a.id)??null}function z(e){return typeof e=="string"&&/^https?:\/\//.test(e)}const I="license:cache",se=60*60*1e3,le=24*60*60*1e3,ce=7*24*60*60*1e3;async function T(){return(await chrome.storage.local.get(I))[I]??null}async function k(e){await chrome.storage.local.set({[I]:e})}async function A(){await chrome.storage.local.remove(I)}function de(e,t=Date.now()){const a=t-e.validatedAt;return a<se?"fresh":a<le?"stale":a<ce?"grace":"expired"}const p=oe("license-gate"),ue="https://api.wcagcheckr.com",Z="wcagcheckr",he=`${ue}/v1/products/${Z}/license/validate`,me=1e4,pe=`https://${Z}.com/upgrade`,R="trialStartedAt",L="licenseToken";async function ee(){let t=(await chrome.storage.local.get(R))[R];return t||(t=Date.now(),await chrome.storage.local.set({[R]:t})),t}function ge(e,t=Date.now()){return t-e>X*864e5}async function F(){return(await chrome.storage.local.get(L))[L]??null}async function fe(e){await chrome.storage.local.set({[L]:e})}async function C(e){const t=await fetch(he,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({token:e}),signal:AbortSignal.timeout(me)});if(!t.ok)throw new Error(`validate http ${t.status}`);return{type:"LICENSE_VALIDATE_RESPONSE",...await t.json()}}async function v(e){const t=await T();if(t&&t.token===e){const n=de(t);if(n==="fresh")return t.response;if(n==="stale")return C(e).then(i=>k({token:e,validatedAt:Date.now(),response:i})).catch(i=>p.warn("background refresh failed",i)),t.response;if(n==="grace")try{const i=await C(e);return await k({token:e,validatedAt:Date.now(),response:i}),i}catch(i){return p.warn("grace-period revalidation failed; using last-known-good",i),t.response}}const a=await C(e);return await k({token:e,validatedAt:Date.now(),response:a}),a}function be(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 w(){const e=await F();if(e)try{const a=await v(e);if(a.valid&&a.plan)return be(a.plan)}catch(a){p.warn("validation failed, falling back",a)}const t=await ee();return ge(t)?"free":"trial"}async function ye(){const e=await T(),t=e==null?void 0:e.response.trialEndsAt;if(t){const i=new Date(t).getTime()-Date.now();return Math.max(0,Math.ceil(i/864e5))}const a=await ee(),n=X*864e5-(Date.now()-a);return Math.max(0,Math.ceil(n/864e5))}async function we(){const e=await T(),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 ve(e,t){const a=re[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 dt(){const e=[];e.push(x("TIER_GET",async i=>{i.forceRefresh&&await A();const r=await w(),o={type:"TIER_GET_RESPONSE",tier:r};if(r==="trial")o.trialDaysRemaining=await ye();else if(r==="team"){const{seatsUsed:s,seatsTotal:l}=await we();s!==void 0&&(o.seatsUsed=s),l!==void 0&&(o.seatsTotal=l)}if(r!=="trial"){const s=await T();if(s!=null&&s.response.valid&&s.response.plan){if(o.planCode=s.response.plan,s.response.plan==="solo-single-month"&&s.response.expiresAt){const l=new Date(s.response.expiresAt).getTime()-Date.now();o.licenseDaysRemaining=Math.max(0,Math.ceil(l/864e5))}s.response.pastDueSince&&(o.pastDue=!0)}}return o})),e.push(x("LICENSE_CHECK_REQUEST",async i=>{const r=await w(),o=ve(r,i.feature);return{type:"LICENSE_CHECK_RESPONSE",allowed:o,tier:r,reason:o?void 0:`${i.feature} requires a higher tier`,upgradeUrl:o?void 0:pe}})),e.push(x("LICENSE_VALIDATE_REQUEST",async i=>{try{if(i.forceRefresh){await A();const r=await v(i.token);return D({type:"LICENSE_CHANGED_EVENT",tier:await w()}),r}return await v(i.token)}catch(r){return p.error("validate failed",r),{type:"LICENSE_VALIDATE_RESPONSE",valid:!1,plan:null,email:null,status:null,expiresAt:null}}})),e.push(x("LICENSE_SET_REQUEST",async i=>{await fe(i.token),await A();let r=!1,o={ok:!1,reason:"not-attempted"};try{const l=await v(i.token);if(await k({token:i.token,validatedAt:Date.now(),response:l}),r=l.valid===!0,r){const c=await B(i.token);c.ok?o={ok:!0,seatsUsed:c.seatsUsed,seatsTotal:c.seatsTotal,overCapacity:c.overCapacity}:o=c}}catch(l){p.warn("validation after set failed",l)}const s=await w();return D({type:"LICENSE_CHANGED_EVENT",tier:s}),{type:"LICENSE_SET_RESPONSE",validated:r,seatClaim:o}})),(async()=>{try{const i=await F();i&&await B(i)}catch(i){p.debug("boot-time seat claim failed",i)}})();const t="license:periodic-refresh",a=60;chrome.alarms.get(t,i=>{i||chrome.alarms.create(t,{periodInMinutes:a})});const n=async i=>{if(i.name===t)try{const r=await F();if(!r)return;await A();const o=await v(r);await k({token:r,validatedAt:Date.now(),response:o}),D({type:"LICENSE_CHANGED_EVENT",tier:await w()}),p.debug("periodic license refresh complete")}catch(r){p.warn("periodic license refresh failed",r)}};return chrome.alarms.onAlarm.addListener(n),e.push(()=>chrome.alarms.onAlarm.removeListener(n)),p.info("handlers registered"),()=>e.forEach(i=>i())}const ke={"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 -->
2
+ <img src="/assets/logo.png" alt="">
3
+
4
+ <!-- Passing — descriptive alt -->
5
+ <img src="/assets/logo.png" alt="Acme Corp logo">
6
+
7
+ <!-- Decorative (rare) — empty alt + aria-hidden -->
8
+ <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 -->
9
+ <img src="/team/cliff.jpg" alt="Cliff C. - Founder of Locustware">
10
+ <!-- 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 -->
11
+ <h2>More info</h2>
12
+ <p>Our 2024 financial results show ...</p>
13
+
14
+ <!-- Passing -->
15
+ <h2>2024 financial results</h2>
16
+ <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 -->
17
+ <p>Click the green button on the right to proceed.</p>
18
+
19
+ <!-- Passing -->
20
+ <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 -->
21
+ <div role="button" onclick="...">Submit</div>
22
+
23
+ <!-- Passing — use the native element -->
24
+ <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 -->
25
+ <img src="/banners/big-quote.png" alt="Customer testimonial: 'Best service ever.'">
26
+
27
+ <!-- Passing — real text -->
28
+ <figure class="testimonial">
29
+ <blockquote>Best service ever.</blockquote>
30
+ <figcaption>— a happy customer</figcaption>
31
+ </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 -->
32
+ <tr style="background:#fee">Failed run</tr>
33
+ <tr style="background:#efe">Passed run</tr>
34
+
35
+ <!-- Passing — colour + icon + text -->
36
+ <tr><td>❌ Failed</td>...</tr>
37
+ <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 -->
38
+ <p>Our pricing has changed. <a href="/pricing">Click here</a>.</p>
39
+
40
+ <!-- Passing — link text is self-describing -->
41
+ <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 -->
42
+ <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>
43
+
44
+ <!-- Passing — chunked with subheadings -->
45
+ <h3>1. Requirements</h3>
46
+ <p>We start by gathering your specifics.</p>
47
+ <h3>2. Design</h3>
48
+ <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 -->
49
+ <html lang="fr"><body>...English content...</body></html>
50
+
51
+ <!-- Passing — correct page-level declaration -->
52
+ <html lang="en"><body>...English content...</body></html>
53
+
54
+ <!-- Passing — passage-level override for non-default language -->
55
+ <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 */
56
+ .container { width: 1024px; }
57
+
58
+ /* Passing: use max-width and let content reflow */
59
+ .container { max-width: 1024px; width: 100%; }
60
+
61
+ /* Prevent images / media from forcing overflow */
62
+ img, video, iframe { max-width: 100%; height: auto; }
63
+
64
+ /* Avoid hard nowrap on long inline content */
65
+ .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 */
66
+ .muted { color: #d3d3d3; }
67
+
68
+ /* Passing: dark slate on white = 12.6:1 */
69
+ .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 -->
70
+ <img src="divider.svg" alt="" />
71
+
72
+ <!-- Informative -->
73
+ <img src="chart.png" alt="Q3 revenue rose 42%" />`},label:{summary:'Every form input needs a programmatic label. Either wrap the input in a <label>, or use <label for="..."> matching the input id, or use aria-label / aria-labelledby. Placeholder text alone is NOT a label.',snippetLang:"html",snippet:`<!-- ✅ Wrapping label -->
74
+ <label>Email <input type="email" name="email" /></label>
75
+
76
+ <!-- ✅ for/id pair -->
77
+ <label for="email">Email</label>
78
+ <input id="email" type="email" name="email" />
79
+
80
+ <!-- ✅ aria-label when visual label isn't possible -->
81
+ <input type="search" aria-label="Search products" />`},"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 -->
82
+ <button>Save changes</button>
83
+
84
+ <!-- ✅ Icon-only with aria-label -->
85
+ <button aria-label="Close dialog">
86
+ <svg aria-hidden="true">...</svg>
87
+ </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 -->
88
+ <a href="/post/42">Read more</a>
89
+
90
+ <!-- ✅ Specific -->
91
+ <a href="/post/42">Read more about Q3 revenue</a>
92
+
93
+ <!-- ✅ Or with aria-label -->
94
+ <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:`<!-- ❌ -->
95
+ <h1>Page title</h1>
96
+ <h3>Section</h3>
97
+
98
+ <!-- ✅ -->
99
+ <h1>Page title</h1>
100
+ <h2>Section</h2>`},region:{summary:"Wrap top-level content in a landmark element so screen reader users can navigate by region. Use <main> for primary content, <nav> for navigation, <aside> for complementary content, <footer> for the page footer.",snippetLang:"html",snippet:`<header>...</header>
101
+ <nav aria-label="Primary">...</nav>
102
+ <main>
103
+ <h1>Page title</h1>
104
+ ...
105
+ </main>
106
+ <footer>...</footer>`},"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>
107
+ <header>...</header>
108
+ <main>
109
+ <!-- primary content -->
110
+ </main>
111
+ <footer>...</footer>
112
+ </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 -->
113
+ <div role="checkbox">Subscribe</div>
114
+
115
+ <!-- ✅ -->
116
+ <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 -->
117
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
118
+
119
+ <!-- ❌ Blocks zoom -->
120
+ <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 */
121
+ .icon-button {
122
+ width: 24px;
123
+ height: 24px;
124
+ /* Ensures clickable area is 44×44 */
125
+ padding: 10px;
126
+ background-clip: content-box;
127
+ }`},"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.'},bypass:{summary:'Provide a way for keyboard users to skip past repetitive page content. Standard pattern: a "Skip to main content" link as the first focusable element, hidden offscreen but visible on focus.',snippetLang:"html",snippet:`<a href="#main" class="skip-link">Skip to main content</a>
128
+ <!-- ... -->
129
+ <main id="main">...</main>`}};function ut(e){return ke[e]??null}const xe=/:nth-(?:last-)?(?:child|of-type)\([^)]+\)/g,Ae=/\[id="[^"]*"\]/g,Ie=/\[data-testid="[^"]*"\]/g,Se=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 Te(e){return e&&e.replace(xe,"").replace(Ae,"").replace(Ie,"").replace(/\s+/g," ").trim()}function Ee(e,t){return Se.has(e)?`${e}::__structural__`:`${e}::${Te(t)}`}const H={minor:0,moderate:1,serious:2,critical:3};function De(e){const t=new Map;for(const a of e){const n=Ee(a.ruleId,a.target.selector),i=t.get(n);if(!i){t.set(n,a);continue}(H[a.impact]??0)>(H[i.impact]??0)&&t.set(n,a)}return Array.from(t.values())}const Re={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"},{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"}]},Ce={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-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"}]},qe={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"}]},We={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"}]},Me={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"}]},Oe={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"}]},Le={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"}]},Fe={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"}]},_e={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"}]},Pe={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"}]},Ne={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"}]},Ue={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"}]},ht=[Re,Ce,qe,We,Me,Oe,Le,Fe,_e,Pe,Ne,Ue];function mt(e,t){let a=0,n=0,i=0;for(const r of t.steps){const o=e.steps[r.id];o&&(o.status==="pass"?a++:o.status==="fail"?n++:i++)}return{passed:a,failed:n,skipped:i,total:t.steps.length}}const Ge={"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 Ve(e){const t=Ge[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 je={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 pt(e,t){return e==="color-contrast"&&t&&(t.self<.99||t.ancestor<.99)?je:Ve(e)}const q=[{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 Be(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 gt(e,t){const a=Be(e),n={critical:0,serious:0,moderate:0,minor:0,unique:a.length};for(const d of a)n[d.impact]++;let i;t&&(i=ze(t.runs,t.workflows),n.serious+=i.failedRequired,n.moderate+=i.failedAdvisory,n.unique+=i.failedRequired+i.failedAdvisory);const r=new Map;for(const d of q)for(const h of d.rules)r.set(h,d);const o=new Map;for(const d of q)o.set(d.id,{count:0,rules:new Set});for(const d of De(a)){const h=r.get(d.ruleId);if(!h)continue;const m=o.get(h.id);m.count++,m.rules.add(d.ruleId)}const s=q.map(d=>{const h=o.get(d.id);let m="pass";return h.count>0?m="fail":d.needsManualCheck&&(m="unchecked"),{id:d.id,label:d.label,status:m,violationCount:h.count,rules:Array.from(h.rules),needsManualCheck:d.needsManualCheck}}),l=n.critical>0,c=n.serious>0;let u;n.critical>=5?u="F":n.critical>=1?u="D":n.serious>=5||n.serious>=1?u="C":n.moderate>=3||n.moderate>=1?u="B":(n.minor>=1,u="A");const b=s.filter(d=>d.status==="fail").length;let g;return n.critical>0||b>=4?g="critical":n.serious>=3||b>=2?g="high":n.serious>=1||n.moderate>=3?g="moderate":g="low",{letter:u,risk:g,caps:{cappedAtC:l,cappedAtB:c},totals:n,categories:s,manual:i}}function ze(e,t){new Map(t.map(s=>[s.id,s]));const a=new Map(e.map(s=>[s.workflowId,s]));let n=0,i=0,r=0;const o=t.map(s=>{const l=a.get(s.id);let c=0,u=0,b=0,g=0,d=0;for(const h of s.steps){const m=l==null?void 0:l.steps[h.id];m&&(d++,m.status==="pass"?c++:m.status==="skip"?g++:m.status==="fail"&&(h.severity==="required"?u++:b++))}return d===s.steps.length&&r++,n+=u,i+=b,{workflowId:s.id,name:s.name,passed:c,failedRequired:u,failedAdvisory:b,skipped:g,answered:d,total:s.steps.length}});return{workflowsCompleted:r,workflowsTotal:t.length,failedRequired:n,failedAdvisory:i,perWorkflow:o}}const ft={low:"Low exposure — no critical or serious violations detected. Manual checks still recommended; automation catches ~30–50% of WCAG issues.",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."},bt={low:"✓ Off the radar",moderate:"⚡ Some lawsuit exposure",high:"⚠️ High target risk",critical:"🚨 Lawsuit magnet"},yt={low:"Your site clears the automated checks ADA-targeting tools use to identify lawsuit victims. Targeting bots cover roughly half of WCAG — switch to Developer mode for the Deep Scan to verify full compliance and stay ahead of human plaintiffs.",moderate:"Some failures. Not the easiest target, but the issues here are exactly what automated targeting tools look for. Catch these plus the ~50% of WCAG issues automation alone misses by running the Deep Scan in Developer mode.",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 Deep Scan with 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 Deep Scan that lists every issue plus fix-it guidance for each."},wt={A:"No critical, serious, or moderate violations detected. Note: automation catches ~30–50% of WCAG; run the Guided Tests workflows to fully validate.",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."},S="inflight:audit";async function te(){return(await chrome.storage.local.get(S))[S]??null}async function ae(e){await chrome.storage.local.set({[S]:e})}async function vt(){await chrome.storage.local.remove(S)}async function kt(){const e=await te();e&&e.state==="running"&&await ae({...e,state:"interrupted",lastProgressAt:new Date().toISOString()})}async function xt(e){const t=await te();t&&await ae({...t,...e,lastProgressAt:new Date().toISOString()})}function At(){return crypto.randomUUID()}const _=(e,t)=>t.some(a=>e instanceof a);let K,Y;function He(){return K||(K=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])}function Ke(){return Y||(Y=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])}const P=new WeakMap,W=new WeakMap,E=new WeakMap;function Ye(e){const t=new Promise((a,n)=>{const i=()=>{e.removeEventListener("success",r),e.removeEventListener("error",o)},r=()=>{a(f(e.result)),i()},o=()=>{n(e.error),i()};e.addEventListener("success",r),e.addEventListener("error",o)});return E.set(t,e),t}function $e(e){if(P.has(e))return;const t=new Promise((a,n)=>{const i=()=>{e.removeEventListener("complete",r),e.removeEventListener("error",o),e.removeEventListener("abort",o)},r=()=>{a(),i()},o=()=>{n(e.error||new DOMException("AbortError","AbortError")),i()};e.addEventListener("complete",r),e.addEventListener("error",o),e.addEventListener("abort",o)});P.set(e,t)}let N={get(e,t,a){if(e instanceof IDBTransaction){if(t==="done")return P.get(e);if(t==="store")return a.objectStoreNames[1]?void 0:a.objectStore(a.objectStoreNames[0])}return f(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 ne(e){N=e(N)}function Je(e){return Ke().includes(e)?function(...t){return e.apply(U(this),t),f(this.request)}:function(...t){return f(e.apply(U(this),t))}}function Qe(e){return typeof e=="function"?Je(e):(e instanceof IDBTransaction&&$e(e),_(e,He())?new Proxy(e,N):e)}function f(e){if(e instanceof IDBRequest)return Ye(e);if(W.has(e))return W.get(e);const t=Qe(e);return t!==e&&(W.set(e,t),E.set(t,e)),t}const U=e=>E.get(e);function Xe(e,t,{blocked:a,upgrade:n,blocking:i,terminated:r}={}){const o=indexedDB.open(e,t),s=f(o);return n&&o.addEventListener("upgradeneeded",l=>{n(f(o.result),l.oldVersion,l.newVersion,f(o.transaction),l)}),a&&o.addEventListener("blocked",l=>a(l.oldVersion,l.newVersion,l)),s.then(l=>{r&&l.addEventListener("close",()=>r()),i&&l.addEventListener("versionchange",c=>i(c.oldVersion,c.newVersion,c))}).catch(()=>{}),s}const Ze=["get","getKey","getAll","getAllKeys","count"],et=["put","add","delete","clear"],M=new Map;function $(e,t){if(!(e instanceof IDBDatabase&&!(t in e)&&typeof t=="string"))return;if(M.get(t))return M.get(t);const a=t.replace(/FromIndex$/,""),n=t!==a,i=et.includes(a);if(!(a in(n?IDBIndex:IDBObjectStore).prototype)||!(i||Ze.includes(a)))return;const r=async function(o,...s){const l=this.transaction(o,i?"readwrite":"readonly");let c=l.store;return n&&(c=c.index(s.shift())),(await Promise.all([c[a](...s),i&&l.done]))[0]};return M.set(t,r),r}ne(e=>({...e,get:(t,a,n)=>$(t,a)||e.get(t,a,n),has:(t,a)=>!!$(t,a)||e.has(t,a)}));const tt=["continue","continuePrimaryKey","advance"],J={},G=new WeakMap,ie=new WeakMap,at={get(e,t){if(!tt.includes(t))return e[t];let a=J[t];return a||(a=J[t]=function(...n){G.set(this,ie.get(this)[t](...n))}),a}};async function*nt(...e){let t=this;if(t instanceof IDBCursor||(t=await t.openCursor(...e)),!t)return;t=t;const a=new Proxy(t,at);for(ie.set(a,t),E.set(a,U(t));t;)yield a,t=await(G.get(a)||t.continue()),G.delete(a)}function Q(e,t){return t===Symbol.asyncIterator&&_(e,[IDBIndex,IDBObjectStore,IDBCursor])||t==="iterate"&&_(e,[IDBIndex,IDBObjectStore])}ne(e=>({...e,get(t,a,n){return Q(t,a)?nt:e.get(t,a,n)},has(t,a){return Q(t,a)||e.has(t,a)}}));const it="wcag-forensic-log",ot=1,y="audits";let O=null;function j(){return O||(O=Xe(it,ot,{upgrade(e){if(!e.objectStoreNames.contains(y)){const t=e.createObjectStore(y,{keyPath:["componentId","capturedAt"]});t.createIndex("byCapturedAt","capturedAt"),t.createIndex("byComponentId","componentId")}}})),O}function V(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?"["+e.map(n=>V(n)).join(",")+"]":"{"+Object.keys(e).sort().map(n=>{const i=e[n];return JSON.stringify(n)+":"+V(i)}).join(",")+"}"}async function rt(e){const t=V(e),a=new TextEncoder().encode(t),n=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(n)).map(i=>i.toString(16).padStart(2,"0")).join("")}async function It(e,t,a){if(e.length===0)return null;const n=e[0],i=n.componentId,r=n.pageUrl??n.scope,o=n.startedAt,s={componentId:i,pageUrl:r,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:o,statesAudited:e.length},c={hash:await rt(s),componentId:i,pageUrl:r,scope:n.scope,grade:t.letter,totals:s.totals,axeVersion:n.axeVersion,capturedAt:o,statesAudited:e.length,durationMs:a};try{await(await j()).put(y,c)}catch(u){return console.warn("[forensic-log] failed to record audit",u),c}return c}async function St(e,t,a){try{const n=await j(),i=await n.get(y,[e,t]);if(!i){console.warn("[forensic-log] no entry to attach receipt to",{componentId:e,capturedAt:t});return}i.receipt=a,await n.put(y,i)}catch(n){console.warn("[forensic-log] failed to attach receipt",n)}}async function Tt(){try{return(await(await j()).getAll(y)).sort((a,n)=>a.capturedAt<n.capturedAt?1:-1)}catch(e){return console.warn("[forensic-log] listAudits failed",e),[]}}export{ht as A,ke as F,wt as L,bt as O,ft as R,Ee as a,yt as b,gt as c,w as d,F as e,St as f,pt as g,vt as h,te as i,ct as j,dt as k,Tt as l,xt as m,At as n,Xe as o,kt as p,ut as q,It as r,ae as s,De as t,mt as u,Ve as v,V as w};
@@ -0,0 +1,6 @@
1
+ import{a as q,R as P,u as W,j as e,b as X,r as c,d as ue,P as U,e as F,f as xe,g as Pe,h as Me,i as R,C as Ue,A as K,k as De,c as $e}from"./styles-kHMb1Lda.js";import{c as me,e as w,o as Ge,f as C,O as Z,h as Oe}from"./crash-reporter-wxu43qbG.js";import{T as Be,P as _,r as Ke,a as Ve,D as ee}from"./state-DnzwwNxZ.js";import{D as V,l as He,c as Je}from"./ai-usage-log-DFkwAfmW.js";function Ye(t){const s=t+"CollectionProvider",[a,n]=q(s),[l,u]=a(s,{collectionRef:{current:null},itemMap:new Map}),d=v=>{const{scope:b,children:i}=v,f=P.useRef(null),j=P.useRef(new Map).current;return e.jsx(l,{scope:b,itemMap:j,collectionRef:f,children:i})};d.displayName=s;const r=t+"CollectionSlot",x=X(r),o=P.forwardRef((v,b)=>{const{scope:i,children:f}=v,j=u(r,i),k=W(b,j.collectionRef);return e.jsx(x,{ref:k,children:f})});o.displayName=r;const m=t+"CollectionItemSlot",p="data-radix-collection-item",h=X(m),y=P.forwardRef((v,b)=>{const{scope:i,children:f,...j}=v,k=P.useRef(null),A=W(b,k),T=u(m,i);return P.useEffect(()=>(T.itemMap.set(k,{ref:k,...j}),()=>void T.itemMap.delete(k))),e.jsx(h,{[p]:"",ref:A,children:f})});y.displayName=m;function N(v){const b=u(t+"CollectionConsumer",v);return P.useCallback(()=>{const f=b.collectionRef.current;if(!f)return[];const j=Array.from(f.querySelectorAll(`[${p}]`));return Array.from(b.itemMap.values()).sort((T,L)=>j.indexOf(T.ref.current)-j.indexOf(L.ref.current))},[b.collectionRef,b.itemMap])}return[{Provider:d,Slot:o,ItemSlot:y},N,n]}var We=c.createContext(void 0);function pe(t){const s=c.useContext(We);return t||s||"ltr"}var H="rovingFocusGroup.onEntryFocus",ze={bubbles:!1,cancelable:!0},$="RovingFocusGroup",[z,he,qe]=Ye($),[Qe,be]=q($,[qe]),[Xe,Ze]=Qe($),fe=c.forwardRef((t,s)=>e.jsx(z.Provider,{scope:t.__scopeRovingFocusGroup,children:e.jsx(z.Slot,{scope:t.__scopeRovingFocusGroup,children:e.jsx(et,{...t,ref:s})})}));fe.displayName=$;var et=c.forwardRef((t,s)=>{const{__scopeRovingFocusGroup:a,orientation:n,loop:l=!1,dir:u,currentTabStopId:d,defaultCurrentTabStopId:r,onCurrentTabStopIdChange:x,onEntryFocus:o,preventScrollOnEntryFocus:m=!1,...p}=t,h=c.useRef(null),y=W(s,h),N=pe(u),[v,b]=xe({prop:d,defaultProp:r??null,onChange:x,caller:$}),[i,f]=c.useState(!1),j=Pe(o),k=he(a),A=c.useRef(!1),[T,L]=c.useState(0);return c.useEffect(()=>{const g=h.current;if(g)return g.addEventListener(H,j),()=>g.removeEventListener(H,j)},[j]),e.jsx(Xe,{scope:a,orientation:n,dir:N,loop:l,currentTabStopId:v,onItemFocus:c.useCallback(g=>b(g),[b]),onItemShiftTab:c.useCallback(()=>f(!0),[]),onFocusableItemAdd:c.useCallback(()=>L(g=>g+1),[]),onFocusableItemRemove:c.useCallback(()=>L(g=>g-1),[]),children:e.jsx(U.div,{tabIndex:i||T===0?-1:0,"data-orientation":n,...p,ref:y,style:{outline:"none",...t.style},onMouseDown:F(t.onMouseDown,()=>{A.current=!0}),onFocus:F(t.onFocus,g=>{const S=!A.current;if(g.target===g.currentTarget&&S&&!i){const I=new CustomEvent(H,ze);if(g.currentTarget.dispatchEvent(I),!I.defaultPrevented){const E=k().filter(M=>M.focusable),_e=E.find(M=>M.active),Fe=E.find(M=>M.id===v),Le=[_e,Fe,...E].filter(Boolean).map(M=>M.ref.current);ve(Le,m)}}A.current=!1}),onBlur:F(t.onBlur,()=>f(!1))})})}),ge="RovingFocusGroupItem",ye=c.forwardRef((t,s)=>{const{__scopeRovingFocusGroup:a,focusable:n=!0,active:l=!1,tabStopId:u,children:d,...r}=t,x=ue(),o=u||x,m=Ze(ge,a),p=m.currentTabStopId===o,h=he(a),{onFocusableItemAdd:y,onFocusableItemRemove:N,currentTabStopId:v}=m;return c.useEffect(()=>{if(n)return y(),()=>N()},[n,y,N]),e.jsx(z.ItemSlot,{scope:a,id:o,focusable:n,active:l,children:e.jsx(U.span,{tabIndex:p?0:-1,"data-orientation":m.orientation,...r,ref:s,onMouseDown:F(t.onMouseDown,b=>{n?m.onItemFocus(o):b.preventDefault()}),onFocus:F(t.onFocus,()=>m.onItemFocus(o)),onKeyDown:F(t.onKeyDown,b=>{if(b.key==="Tab"&&b.shiftKey){m.onItemShiftTab();return}if(b.target!==b.currentTarget)return;const i=at(b,m.orientation,m.dir);if(i!==void 0){if(b.metaKey||b.ctrlKey||b.altKey||b.shiftKey)return;b.preventDefault();let j=h().filter(k=>k.focusable).map(k=>k.ref.current);if(i==="last")j.reverse();else if(i==="prev"||i==="next"){i==="prev"&&j.reverse();const k=j.indexOf(b.currentTarget);j=m.loop?nt(j,k+1):j.slice(k+1)}setTimeout(()=>ve(j))}}),children:typeof d=="function"?d({isCurrentTabStop:p,hasTabStop:v!=null}):d})})});ye.displayName=ge;var tt={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"};function st(t,s){return s!=="rtl"?t:t==="ArrowLeft"?"ArrowRight":t==="ArrowRight"?"ArrowLeft":t}function at(t,s,a){const n=st(t.key,a);if(!(s==="vertical"&&["ArrowLeft","ArrowRight"].includes(n))&&!(s==="horizontal"&&["ArrowUp","ArrowDown"].includes(n)))return tt[n]}function ve(t,s=!1){const a=document.activeElement;for(const n of t)if(n===a||(n.focus({preventScroll:s}),document.activeElement!==a))return}function nt(t,s){return t.map((a,n)=>t[(s+n)%t.length])}var rt=fe,ot=ye,B="Tabs",[lt]=q(B,[be]),je=be(),[it,Q]=lt(B),Ne=c.forwardRef((t,s)=>{const{__scopeTabs:a,value:n,onValueChange:l,defaultValue:u,orientation:d="horizontal",dir:r,activationMode:x="automatic",...o}=t,m=pe(r),[p,h]=xe({prop:n,onChange:l,defaultProp:u??"",caller:B});return e.jsx(it,{scope:a,baseId:ue(),value:p,onValueChange:h,orientation:d,dir:m,activationMode:x,children:e.jsx(U.div,{dir:m,"data-orientation":d,...o,ref:s})})});Ne.displayName=B;var ke="TabsList",Se=c.forwardRef((t,s)=>{const{__scopeTabs:a,loop:n=!0,...l}=t,u=Q(ke,a),d=je(a);return e.jsx(rt,{asChild:!0,...d,orientation:u.orientation,dir:u.dir,loop:n,children:e.jsx(U.div,{role:"tablist","aria-orientation":u.orientation,...l,ref:s})})});Se.displayName=ke;var Te="TabsTrigger",we=c.forwardRef((t,s)=>{const{__scopeTabs:a,value:n,disabled:l=!1,...u}=t,d=Q(Te,a),r=je(a),x=Ie(d.baseId,n),o=Ae(d.baseId,n),m=n===d.value;return e.jsx(ot,{asChild:!0,...r,focusable:!l,active:m,children:e.jsx(U.button,{type:"button",role:"tab","aria-selected":m,"aria-controls":o,"data-state":m?"active":"inactive","data-disabled":l?"":void 0,disabled:l,id:x,...u,ref:s,onMouseDown:F(t.onMouseDown,p=>{!l&&p.button===0&&p.ctrlKey===!1?d.onValueChange(n):p.preventDefault()}),onKeyDown:F(t.onKeyDown,p=>{[" ","Enter"].includes(p.key)&&d.onValueChange(n)}),onFocus:F(t.onFocus,()=>{const p=d.activationMode!=="manual";!m&&!l&&p&&d.onValueChange(n)})})})});we.displayName=Te;var Ce="TabsContent",Ee=c.forwardRef((t,s)=>{const{__scopeTabs:a,value:n,forceMount:l,children:u,...d}=t,r=Q(Ce,a),x=Ie(r.baseId,n),o=Ae(r.baseId,n),m=n===r.value,p=c.useRef(m);return c.useEffect(()=>{const h=requestAnimationFrame(()=>p.current=!1);return()=>cancelAnimationFrame(h)},[]),e.jsx(Me,{present:l||m,children:({present:h})=>e.jsx(U.div,{"data-state":m?"active":"inactive","data-orientation":r.orientation,role:"tabpanel","aria-labelledby":x,hidden:!h,id:o,tabIndex:0,...d,ref:s,style:{...t.style,animationDuration:p.current?"0s":void 0},children:h&&u})})});Ee.displayName=Ce;function Ie(t,s){return`${t}-trigger-${s}`}function Ae(t,s){return`${t}-content-${s}`}var ct=Ne,dt=Se,ut=we,G=Ee;const xt=["default","hover","focus","focus-visible","active","disabled"],mt=["light","dark","forced-colors-active","forced-colors-none"],pt=["ltr","rtl"];function ht({value:t,onChange:s}){function a(l,u){return l.includes(u)?l.filter(d=>d!==u):[...l,u]}function n(){const l=t.ariaVariations.length===0?1:t.ariaVariations.length;return t.pseudoStates.length*l*t.themes.length*t.directions.length*t.breakpoints.length}return e.jsxs("div",{className:"space-y-3 border border-slate-200 rounded p-3 bg-white",children:[e.jsx(O,{label:"Pseudo-states",all:xt,selected:t.pseudoStates,onToggle:l=>s({...t,pseudoStates:a(t.pseudoStates,l)})}),e.jsx(O,{label:"Themes",all:mt,selected:t.themes,onToggle:l=>s({...t,themes:a(t.themes,l)})}),e.jsx(O,{label:"Directions",all:pt,selected:t.directions,onToggle:l=>s({...t,directions:a(t.directions,l)})}),e.jsx(O,{label:"Breakpoints",all:t.breakpointPresets.map(l=>l.id),selected:t.breakpoints,onToggle:l=>s({...t,breakpoints:a(t.breakpoints,l)})}),e.jsxs("p",{className:"text-xs text-slate-500 pt-2 border-t border-slate-100",children:[e.jsx("strong",{children:n()})," audits per component"]})]})}function O({label:t,all:s,selected:a,onToggle:n}){return e.jsxs("div",{children:[e.jsx("div",{className:"text-xs font-medium mb-1.5",children:t}),e.jsx("div",{className:"flex flex-wrap gap-1.5",children:s.map(l=>{const u=a.includes(l);return e.jsx("button",{type:"button",onClick:()=>n(l),className:u?"text-xs px-2 py-1 rounded bg-brand-500 text-white":"text-xs px-2 py-1 rounded bg-slate-100 text-slate-700 hover:bg-slate-200",children:l},l)})})]})}function bt({value:t,onChange:s}){const[a,n]=c.useState(""),[l,u]=c.useState("");function d(o){o.preventDefault();const m=a.trim(),p=l.trim();!m||!p||(s({...t,[m]:p}),n(""),u(""))}function r(o){const m={...t};delete m[o],s(m)}const x=Object.entries(t);return e.jsxs("div",{className:"space-y-3 border border-slate-200 rounded p-3 bg-white",children:[x.length===0?e.jsx("p",{className:"text-xs text-slate-500 italic",children:"no aliases"}):e.jsx("ul",{className:"space-y-1",children:x.map(([o,m])=>e.jsxs("li",{className:"text-xs flex items-center justify-between gap-2 border border-slate-100 rounded p-1.5",children:[e.jsxs("span",{className:"font-mono truncate",title:`${o} → ${m}`,children:[o," ",e.jsx("span",{className:"text-slate-500",children:"→"})," ",m]}),e.jsx("button",{onClick:()=>r(o),className:"text-slate-500 hover:text-red-600 shrink-0","aria-label":`Remove alias ${o}`,children:"×"})]},o))}),e.jsxs("form",{onSubmit:d,className:"grid grid-cols-[1fr_1fr_auto] gap-2 items-end",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-xs text-slate-500 mb-1",children:"From"}),e.jsx("input",{type:"text",value:a,onChange:o=>n(o.target.value),placeholder:"https://example.com::testid:btn-a",className:"w-full text-xs border border-slate-300 rounded px-2 py-1 font-mono"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-xs text-slate-500 mb-1",children:"Treat as"}),e.jsx("input",{type:"text",value:l,onChange:o=>u(o.target.value),placeholder:"https://example.com::testid:btn-canonical",className:"w-full text-xs border border-slate-300 rounded px-2 py-1 font-mono"})]}),e.jsx("button",{type:"submit",className:"text-xs px-3 py-1 bg-brand-500 text-white rounded disabled:opacity-50",disabled:!a.trim()||!l.trim(),children:"Add"})]})]})}const te=me("billing-portal-client"),ft="https://api.wcagcheckr.com",gt="wcagcheckr",yt=15e3;async function vt(t){const s=`${ft}/v1/products/${gt}/billing-portal`;try{const a=await fetch(s,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({licenseToken:t.licenseToken}),signal:AbortSignal.timeout(yt)}),n=await a.json().catch(()=>null);return a.ok?n!=null&&n.url?{ok:!0,url:n.url}:{ok:!1,error:"malformed response — no url",status:a.status}:(te.warn(`billing portal HTTP ${a.status}`,n),{ok:!1,status:a.status,error:(n==null?void 0:n.hint)??(n==null?void 0:n.error)??`HTTP ${a.status}`})}catch(a){return te.warn("billing portal request failed",a),{ok:!1,error:a instanceof Error?a.message:String(a)}}}const se=me("subscription-resume-client"),jt="https://api.wcagcheckr.com",Nt="wcagcheckr",kt=1e4;async function St(t){try{const s=await fetch(`${jt}/v1/products/${Nt}/subscription/resume`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({licenseToken:t.licenseToken}),signal:AbortSignal.timeout(kt)}),a=await s.json().catch(()=>null);return s.ok?{ok:!0,alreadyActive:(a==null?void 0:a.alreadyActive)===!0}:(se.warn(`resume http ${s.status}`,a),{ok:!1,status:s.status,error:(a==null?void 0:a.hint)??(a==null?void 0:a.error)??`HTTP ${s.status}`})}catch(s){return se.warn("resume request failed",s),{ok:!1,error:s instanceof Error?s.message:String(s)}}}const Tt={trial:"Trial",free:"Free",solo:"Solo",team:"Team"},wt={free:"Free",solo:"Solo (monthly)","solo-yearly":"Solo (yearly)","solo-single-month":"Solo (single month — non-recurring)",team:"Team (5 seats, monthly)","team-yearly":"Team (5 seats, yearly)","team-15":"Team (15 seats, monthly)","team-15-yearly":"Team (15 seats, yearly)"},Ct=new Set(["solo-single-month"]);function Et(t){if(!t)return null;const s=new Date(t).getTime()-Date.now();return Number.isFinite(s)?Math.ceil(s/(24*60*60*1e3)):null}function D(t){return t?wt[t]??t:"—"}function J(t){if(!t)return"no expiry on file";try{return new Date(t).toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}catch{return t}}function It({tier:t,onTierChange:s}){const[a,n]=c.useState(null),[l,u]=c.useState(null),[d,r]=c.useState(!1),[x,o]=c.useState(""),[m,p]=c.useState(!1),[h,y]=c.useState(null),[N,v]=c.useState(!1),[b,i]=c.useState(!1),f=c.useCallback(async g=>{const I=(await chrome.storage.local.get("licenseToken")).licenseToken??null;if(n(I),!I){u(null);return}try{const E=await w({type:"LICENSE_VALIDATE_REQUEST",token:I,forceRefresh:(g==null?void 0:g.forceRefresh)===!0});u(E)}catch{u(null)}},[]);c.useEffect(()=>(f({forceRefresh:!0}),Ge("LICENSE_CHANGED_EVENT",()=>{f()})),[f]);async function j(){if(x.trim()){p(!0),y(null);try{const g=await w({type:"LICENSE_SET_REQUEST",token:x.trim()}),S=await w({type:"LICENSE_VALIDATE_REQUEST",token:x.trim()});if(S.valid&&S.plan){const I=_t(S.plan);s(I);let E=`Activated as ${D(S.plan)}.`;g.seatClaim.ok?g.seatClaim.overCapacity&&(E=`Activated as ${D(S.plan)}. Note: this license is currently OVER its seat cap (${g.seatClaim.seatsUsed} of ${g.seatClaim.seatsTotal} seats). Release one or upgrade.`):g.seatClaim.reason==="cap-reached"?E=`Activated as ${D(S.plan)}, BUT this license is at its seat capacity (${g.seatClaim.seatsUsed} of ${g.seatClaim.seatsTotal} seats used). Release a seat from another device, or upgrade to a higher-seat plan. This device's seat won't be active until you do.`:g.seatClaim.reason==="network"&&(E=`Activated as ${D(S.plan)}. (Seat-tracking ping failed — retrying in the background.)`),y(E),g.seatClaim.ok&&!g.seatClaim.overCapacity?R.polite(E):R.assertive(E),o(""),r(!1),await f()}else{const I="Token did not validate. Check the value and try again.";y(I),R.assertive(I)}}catch(g){const S=g instanceof Error?g.message:String(g);y(S),R.assertive(S)}finally{p(!1)}}}async function k(){if(a)try{await navigator.clipboard.writeText(a),v(!0),setTimeout(()=>v(!1),2e3)}catch{}}async function A(){if(window.confirm(`Remove the active license token from this extension?
2
+
3
+ Your subscription stays active server-side — you can paste the same token back in any time. The extension will fall back to trial / free until then.`)){a&&await Ke(a),await chrome.storage.local.remove("licenseToken"),n(null),u(null);try{const S=await w({type:"TIER_GET"});s(S.tier)}catch{s("free")}R.polite("License token removed.")}}const T=Be[t],L=a!==null&&(l==null?void 0:l.valid)===!0;return e.jsxs("div",{className:"border border-slate-200 rounded p-3 bg-white space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs text-slate-500",children:"Current tier"}),e.jsx("span",{className:"text-xs px-2 py-0.5 rounded-full bg-slate-100 text-slate-700",children:Tt[t]})]}),e.jsxs("ul",{className:"text-xs text-slate-600 space-y-1",children:[e.jsxs("li",{children:["Components:"," ",Number.isFinite(T.maxComponents)?T.maxComponents:"unlimited"]}),e.jsxs("li",{children:["Baselines:"," ",Number.isFinite(T.maxBaselines)?T.maxBaselines:"unlimited"]}),e.jsxs("li",{children:["State matrix: ",T.stateMatrix]}),e.jsxs("li",{children:["Storybook auto-iterate: ",T.storybookAutoIterate?"yes":"no"]}),e.jsxs("li",{children:["JSON export: ",T.exportJson?"yes":"no"]}),e.jsxs("li",{children:["SARIF / JUnit export: ",T.exportSarif?"yes":"no"]})]}),L&&l?e.jsx(At,{token:a,info:l,copied:N,onCopy:k,onRemove:A,onUseDifferent:()=>r(!0),onManageSubscription:async()=>{const g=await vt({licenseToken:a});g.ok?window.open(g.url,"_blank","noopener,noreferrer"):(y(`Couldn't open billing portal: ${g.error}`),R.assertive("Could not open billing portal."))},onResumeSubscription:async()=>{const g=await St({licenseToken:a});if(g.ok){const S=g.alreadyActive?"Subscription was already active — no change needed.":"Subscription resumed. Cancellation has been reversed.";y(S),R.polite(S),await f({forceRefresh:!0})}else y(`Couldn't resume subscription: ${g.error}`),R.assertive("Could not resume subscription.")},onRenew:()=>i(!0),message:h}):a?e.jsx(Rt,{token:a,onRemove:A,onUseDifferent:()=>r(!0)}):e.jsx(ae,{token:x,onTokenChange:o,submitting:m,message:h,onSubmit:j}),L&&d&&e.jsx(ae,{token:x,onTokenChange:o,submitting:m,message:h,onSubmit:j,replacing:!0,onCancel:()=>{r(!1),o(""),y(null)}}),!L&&(t==="trial"||t==="free")&&e.jsxs("div",{className:"border-t border-slate-100 pt-3 space-y-2",children:[e.jsxs("p",{className:"text-xs text-slate-600",children:[e.jsx("strong",{children:"Solo"}),": $",_.solo.monthlyUsd,"/mo (recurring) ·"," ","$",_.solo.yearlyUsd,"/yr (save ",Math.round(100-_.solo.yearlyUsd/(_.solo.monthlyUsd*12)*100),"%) ·"," ","$",_.solo.singleMonthUsd," single month (no recurring) · single user."]}),e.jsxs("p",{className:"text-xs text-slate-600",children:[e.jsx("strong",{children:"Team"}),": $",_.team.fiveSeatMonthlyUsd,"/mo for 5 seats ·"," ","$",_.team.fifteenSeatMonthlyUsd,"/mo for 15 seats."]}),e.jsx("button",{type:"button",onClick:()=>i(!0),className:"inline-block text-xs px-3 py-1 bg-brand-500 text-white rounded hover:bg-brand-600",children:"Upgrade →"})]}),e.jsx(Ue,{open:b,onClose:()=>i(!1)})]})}function At({token:t,info:s,copied:a,onCopy:n,onRemove:l,onUseDifferent:u,onManageSubscription:d,onResumeSubscription:r,onRenew:x,message:o}){const m=s.features,p=typeof(m==null?void 0:m.seatsUsed)=="number"?m.seatsUsed:null,h=typeof(m==null?void 0:m.seatsTotal)=="number"?m.seatsTotal:null,y=h!==null,N=p!==null&&h!==null&&p>h,v=p!==null&&h!==null&&p===h,b=s.plan?Ct.has(s.plan):!1,i=Et(s.expiresAt),f=b&&i!==null&&i<=7;return e.jsxs("div",{className:"border-t border-slate-100 pt-3 space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"text-xs font-medium text-slate-700",children:"Active license"}),e.jsx("span",{className:s.cancelAtPeriodEnd?"text-[10px] px-1.5 py-0.5 rounded-full bg-amber-100 text-amber-900":"text-[10px] px-1.5 py-0.5 rounded-full bg-emerald-100 text-emerald-800",children:s.cancelAtPeriodEnd?"cancellation scheduled":s.status??"unknown"})]}),s.pastDueSince&&e.jsxs("div",{className:"bg-rose-50 border border-rose-200 rounded p-2 text-[11px] text-rose-900",children:[e.jsx("strong",{children:"Payment failed."})," Stripe couldn't charge your card. Access continues during the grace period (usually up to 7 days while Stripe retries), then this license will cancel automatically."," ",e.jsx("button",{type:"button",onClick:d,className:"font-medium underline-offset-2 hover:underline",children:"Update your card via the Stripe portal →"})]}),s.cancelAtPeriodEnd&&e.jsxs("div",{className:"bg-amber-50 border border-amber-200 rounded p-2 text-[11px] text-amber-900 space-y-1",children:[e.jsxs("p",{children:[e.jsx("strong",{children:"Cancellation scheduled."})," Your subscription was cancelled via Stripe. Access continues until ",e.jsx("strong",{children:J(s.expiresAt)}),", then this license stops working."]}),e.jsx("button",{type:"button",onClick:r,className:"text-[11px] font-medium underline-offset-2 hover:underline",children:"Resume subscription →"})]}),N&&e.jsxs("div",{className:"bg-rose-50 border border-rose-200 rounded p-2 text-[11px] text-rose-900",children:[e.jsx("strong",{children:"Over seat capacity."})," This license has"," ",e.jsx("strong",{children:p})," seats in use but the plan only includes"," ",e.jsx("strong",{children:h}),". New devices will be rejected. Release a seat from another device, or upgrade to a higher-seat plan via Manage subscription below."]}),v&&!N&&e.jsxs("div",{className:"bg-amber-50 border border-amber-200 rounded p-2 text-[11px] text-amber-900",children:[e.jsx("strong",{children:"At seat capacity."})," All ",e.jsx("strong",{children:h})," seats are in use. The next new device that tries to activate this license will be rejected until a seat is released."]}),e.jsxs("dl",{className:"text-xs text-slate-600 grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1",children:[e.jsx("dt",{className:"text-slate-500",children:"Plan"}),e.jsx("dd",{children:D(s.plan)}),e.jsx("dt",{className:"text-slate-500",children:"Email"}),e.jsx("dd",{className:"truncate",children:s.email??"—"}),e.jsx("dt",{className:"text-slate-500",children:b?"Expires":"Renews / ends"}),e.jsxs("dd",{children:[J(s.expiresAt),i!==null&&i>=0&&i<=14?e.jsx("span",{className:f?"ml-2 text-[10px] text-amber-900 bg-amber-100 rounded px-1.5 py-0.5":"ml-2 text-[10px] text-slate-500",children:i===0?"today":i===1?"1 day left":`${i} days left`}):null]}),s.trialEndsAt?e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-slate-500",children:"Trial ends"}),e.jsx("dd",{children:J(s.trialEndsAt)})]}):null,y?e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-slate-500",children:"Seats"}),e.jsx("dd",{children:p!==null?`${p} of ${h} used`:`${h} total`})]}):null]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-[11px] font-medium text-slate-500 mb-1",children:"Token"}),e.jsxs("div",{className:"flex items-stretch gap-1",children:[e.jsx("code",{className:"flex-1 font-mono text-[11px] bg-slate-100 border border-slate-200 rounded px-2 py-1.5 select-all break-all",children:t}),e.jsx("button",{type:"button",onClick:n,className:"text-[11px] px-2 py-1.5 border border-slate-300 rounded bg-white hover:bg-slate-50 shrink-0",children:a?"Copied ✓":"Copy"})]})]}),e.jsx("div",{className:"pt-1 flex flex-wrap gap-2",children:b?e.jsx("button",{type:"button",onClick:x,className:f?"text-xs px-3 py-1.5 bg-amber-500 text-white rounded hover:bg-amber-600":"text-xs px-3 py-1.5 bg-brand-500 text-white rounded hover:bg-brand-600",title:"Buy another month — extends your existing license, no new token to copy",children:f?`Renew now — $${_.solo.singleMonthUsd} →`:`Buy another month — $${_.solo.singleMonthUsd} →`}):e.jsx("button",{type:"button",onClick:d,className:"text-xs px-3 py-1.5 bg-brand-500 text-white rounded hover:bg-brand-600",title:"Open Stripe's billing portal — change plan or seat count, update card, view invoices, cancel",children:"Manage subscription →"})}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2 pt-1",children:[e.jsx("button",{type:"button",onClick:u,className:"text-[11px] text-brand-600 hover:underline",children:"Use a different token"}),e.jsx("span",{className:"text-slate-300","aria-hidden":"true",children:"·"}),e.jsx("button",{type:"button",onClick:l,className:"text-[11px] text-rose-700 hover:underline",children:"Remove license from this extension"})]}),o&&e.jsx("p",{className:"text-xs text-slate-600 pt-1",children:o})]})}function Rt({token:t,onRemove:s,onUseDifferent:a}){return e.jsxs("div",{className:"border-t border-rose-100 pt-3 space-y-2 bg-rose-50/40 -mx-3 px-3 pb-2 rounded-b",children:[e.jsxs("p",{className:"text-xs text-rose-800",children:[e.jsx("strong",{children:"License token rejected by the server."})," The token may have been revoked, the subscription canceled, or the network is down. Verify your subscription and try a different token — or remove this one and continue on the trial / free tier."]}),e.jsx("p",{className:"text-[11px] text-rose-700 font-mono break-all",children:t}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{type:"button",onClick:a,className:"text-[11px] text-brand-600 hover:underline",children:"Use a different token"}),e.jsx("span",{className:"text-slate-300","aria-hidden":"true",children:"·"}),e.jsx("button",{type:"button",onClick:s,className:"text-[11px] text-rose-700 hover:underline",children:"Remove and continue without a license"})]})]})}function ae({token:t,onTokenChange:s,submitting:a,message:n,onSubmit:l,replacing:u,onCancel:d}){return e.jsxs("div",{className:"border-t border-slate-100 pt-3 space-y-2",children:[e.jsx("label",{htmlFor:"opt-license-token",className:"block text-xs font-medium",children:u?"Replace with a different token":"Activate license token"}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("input",{id:"opt-license-token",type:"text",value:t,onChange:r=>s(r.target.value),placeholder:"00000000-0000-0000-0000-000000000000",className:"flex-1 text-xs border border-slate-300 rounded px-2 py-1 font-mono"}),e.jsx("button",{onClick:l,disabled:a||!t.trim(),className:"text-xs px-3 py-1 bg-brand-500 text-white rounded disabled:opacity-50",children:a?"Validating…":u?"Replace":"Activate"}),u&&d&&e.jsx("button",{type:"button",onClick:d,className:"text-xs px-3 py-1 border border-slate-300 rounded text-slate-700 hover:bg-slate-50",children:"Cancel"})]}),n&&e.jsx("p",{className:"text-xs text-slate-600",children:n})]})}function _t(t){return t==="solo"||t==="solo-yearly"||t==="solo-single-month"?"solo":t==="team"||t==="team-15"||t==="team-yearly"||t==="team-15-yearly"?"team":"free"}const Ft=new Set(Ve.map(t=>t.id));function Lt({presets:t,onChange:s}){const[a,n]=c.useState({id:"",label:"",width:1024,height:768,deviceScaleFactor:1,mobile:!1});function l(r){r.preventDefault();const x=a.id.trim();!x||t.some(o=>o.id===x)||(s([...t,{...a,id:x,label:a.label.trim()||`${x} (${a.width}×${a.height})`}]),n({id:"",label:"",width:1024,height:768,deviceScaleFactor:1,mobile:!1}))}function u(r){s(t.filter(x=>x.id!==r))}function d(r,x){s(t.map(o=>o.id===r?{...o,...x}:o))}return e.jsxs("div",{className:"space-y-3 border border-slate-200 rounded p-3 bg-white",children:[t.length===0?e.jsx("p",{className:"text-xs text-slate-500 italic",children:"no breakpoints"}):e.jsx("ul",{className:"space-y-1.5",children:t.map(r=>{const x=Ft.has(r.id);return e.jsxs("li",{className:"grid grid-cols-[1fr_70px_70px_auto_auto] gap-2 items-center text-xs",children:[e.jsxs("span",{className:"font-medium truncate",title:r.id,children:[r.label,x&&e.jsx("span",{className:"text-slate-500 font-normal ml-1",children:"(default)"})]}),e.jsx("input",{type:"number",value:r.width,onChange:o=>d(r.id,{width:Number(o.target.value)||0}),className:"text-xs border border-slate-300 rounded px-1.5 py-1 w-full","aria-label":`Width for ${r.label}`,min:1}),e.jsx("input",{type:"number",value:r.height,onChange:o=>d(r.id,{height:Number(o.target.value)||0}),className:"text-xs border border-slate-300 rounded px-1.5 py-1 w-full","aria-label":`Height for ${r.label}`,min:1}),e.jsxs("label",{className:"flex items-center gap-1 text-slate-600",children:[e.jsx("input",{type:"checkbox",checked:r.mobile,onChange:o=>d(r.id,{mobile:o.target.checked})}),"mobile"]}),x?e.jsx("span",{className:"w-4","aria-hidden":"true"}):e.jsx("button",{onClick:()=>u(r.id),className:"text-slate-500 hover:text-red-600","aria-label":`Remove ${r.label}`,children:"×"})]},r.id)})}),e.jsxs("form",{onSubmit:l,className:"grid grid-cols-[1fr_1fr_70px_70px_auto] gap-2 items-end pt-3 border-t border-slate-100",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"bp-draft-id",className:"block text-xs text-slate-500 mb-1",children:"ID"}),e.jsx("input",{id:"bp-draft-id",type:"text",value:a.id,onChange:r=>n({...a,id:r.target.value}),placeholder:"laptop",className:"w-full text-xs border border-slate-300 rounded px-2 py-1"})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"bp-draft-label",className:"block text-xs text-slate-500 mb-1",children:"Label"}),e.jsx("input",{id:"bp-draft-label",type:"text",value:a.label,onChange:r=>n({...a,label:r.target.value}),placeholder:"Laptop (1440×900)",className:"w-full text-xs border border-slate-300 rounded px-2 py-1"})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"bp-draft-width",className:"block text-xs text-slate-500 mb-1",children:"W"}),e.jsx("input",{id:"bp-draft-width",type:"number",value:a.width,onChange:r=>n({...a,width:Number(r.target.value)||0}),min:1,className:"w-full text-xs border border-slate-300 rounded px-1.5 py-1"})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"bp-draft-height",className:"block text-xs text-slate-500 mb-1",children:"H"}),e.jsx("input",{id:"bp-draft-height",type:"number",value:a.height,onChange:r=>n({...a,height:Number(r.target.value)||0}),min:1,className:"w-full text-xs border border-slate-300 rounded px-1.5 py-1"})]}),e.jsx("button",{type:"submit",disabled:!a.id.trim()||t.some(r=>r.id===a.id.trim()),className:"text-xs px-3 py-1 bg-brand-500 text-white rounded disabled:opacity-50",children:"Add"})]})]})}function Pt({stateMatrix:t,componentIdAliases:s,onImport:a}){const n=c.useRef(null),[l,u]=c.useState(null);function d(){const x={schemaVersion:1,exportedAt:new Date().toISOString(),stateMatrix:t,componentIdAliases:s},o=new Blob([JSON.stringify(x,null,2)],{type:"application/json"}),m=URL.createObjectURL(o),p=document.createElement("a");p.href=m,p.download=`wcag-auditor-settings-${new Date().toISOString().slice(0,10)}.json`,p.click(),URL.revokeObjectURL(m),u("Exported.")}async function r(x){var o,m;u(null);try{const p=await x.text(),h=JSON.parse(p);if(h.schemaVersion!==1)throw new Error(`Unsupported schemaVersion: ${h.schemaVersion}`);if(!((o=h.stateMatrix)!=null&&o.pseudoStates)||!((m=h.stateMatrix)!=null&&m.breakpointPresets))throw new Error("stateMatrix is malformed");await C({type:"SETTINGS_SET",key:"stateMatrix",value:h.stateMatrix}),await C({type:"SETTINGS_SET",key:"componentIdAliases",value:h.componentIdAliases??{}}),a({stateMatrix:h.stateMatrix,componentIdAliases:h.componentIdAliases??{}}),u("Imported.")}catch(p){u(`Import failed: ${p instanceof Error?p.message:String(p)}`)}}return e.jsxs("div",{className:"border border-slate-200 rounded p-3 bg-white space-y-2",children:[e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:d,className:"text-xs px-3 py-1 bg-slate-100 hover:bg-slate-200 rounded",children:"Export settings"}),e.jsx("button",{onClick:()=>{var x;return(x=n.current)==null?void 0:x.click()},className:"text-xs px-3 py-1 bg-slate-100 hover:bg-slate-200 rounded",children:"Import settings…"}),e.jsx("input",{ref:n,type:"file",accept:"application/json,.json",className:"hidden",onChange:x=>{var m;const o=(m=x.target.files)==null?void 0:m[0];o&&r(o),x.target.value=""}})]}),l&&e.jsx("p",{className:"text-xs text-slate-600",children:l})]})}function Mt(){const[t,s]=c.useState(null);c.useEffect(()=>{chrome.storage.local.get(Z).then(n=>s(n[Z]===!0))},[]);async function a(){const n=!t;s(n),await Oe(n)}return t===null?e.jsx("p",{className:"text-xs text-slate-500",children:"Loading…"}):e.jsx("div",{className:"border border-slate-200 rounded p-3 bg-white space-y-2",children:e.jsxs("label",{className:"flex items-start gap-2 text-xs cursor-pointer",children:[e.jsx("input",{type:"checkbox",checked:t,onChange:a,className:"mt-0.5"}),e.jsxs("span",{children:[e.jsx("span",{className:"font-medium",children:"Send anonymized crash reports"}),e.jsx("span",{className:"block text-slate-500 mt-0.5",children:"When the extension hits an unexpected error, send the error message + redacted stack trace + extension version. URLs, emails, and license tokens are stripped before sending. No audit data, page content, or browsing history is included."})]})]})})}function Ut(){const[t,s]=c.useState(null);c.useEffect(()=>{chrome.storage.local.get(K).then(n=>s(n[K]===!0))},[]);async function a(){const n=!t;s(n),await chrome.storage.local.set({[K]:n})}return t===null?e.jsx("p",{className:"text-xs text-slate-500",children:"Loading…"}):e.jsx("div",{className:"border border-slate-200 rounded p-3 bg-white space-y-2",children:e.jsxs("label",{className:"flex items-start gap-2 text-xs cursor-pointer",children:[e.jsx("input",{type:"checkbox",checked:t,onChange:a,className:"mt-0.5"}),e.jsxs("span",{children:[e.jsx("span",{className:"font-medium",children:"Skip the download confirmation"}),e.jsx("span",{className:"block text-slate-500 mt-0.5",children:"By default, generating a defense or evidence bundle pops up a small confirmation asking whether to download or preview. Enable this to skip the prompt and have the file land directly in your Downloads folder. Keep it off if you'd rather decide each time (recommended for non-technical users — surprise downloads can be alarming)."})]})]})})}const ne="disabledRules";function Dt(){const[t,s]=c.useState(""),[a,n]=c.useState(!1),[l,u]=c.useState(null);c.useEffect(()=>{w({type:"SETTINGS_GET",key:ne}).then(r=>{const x=Array.isArray(r.data)?r.data:[];s(x.join(`
4
+ `)),n(!0)})},[]);function d(r){const x=r.split(`
5
+ `).map(o=>o.trim()).filter(o=>o.length>0);C({type:"SETTINGS_SET",key:ne,value:x}),u(Date.now())}return a?e.jsxs("div",{className:"space-y-2",children:[e.jsxs("p",{className:"text-xs text-slate-500",children:["Rule IDs to skip during audits. One per line. Use exact axe-core rule IDs (e.g.,"," ",e.jsx("code",{className:"bg-slate-100 text-slate-700 px-1 rounded",children:"color-contrast"}),","," ",e.jsx("code",{className:"bg-slate-100 text-slate-700 px-1 rounded",children:"region"}),","," ",e.jsx("code",{className:"bg-slate-100 text-slate-700 px-1 rounded",children:"landmark-one-main"}),")."]}),e.jsx("textarea",{value:t,onChange:r=>s(r.target.value),onBlur:r=>d(r.target.value),rows:6,spellCheck:!1,placeholder:`region
6
+ landmark-one-main`,className:"w-full text-xs font-mono border border-slate-300 rounded p-2 resize-y"}),l&&e.jsxs("p",{className:"text-[11px] text-slate-500",children:["Saved at ",new Date(l).toLocaleTimeString(),"."]}),e.jsxs("p",{className:"text-[11px] text-slate-500",children:["Full rule list:"," ",e.jsx("a",{href:"https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md",target:"_blank",rel:"noreferrer",className:"text-brand-600 hover:underline",children:"axe-core/doc/rule-descriptions.md"})]})]}):e.jsx("div",{className:"text-xs text-slate-500",children:"Loading…"})}const re="githubRepoUrl",$t=/^https:\/\/github\.com\/[^/]+\/[^/]+\/?$/;function Gt(){const[t,s]=c.useState(""),[a,n]=c.useState(!1),[l,u]=c.useState(null);c.useEffect(()=>{w({type:"SETTINGS_GET",key:re}).then(r=>{const x=typeof r.data=="string"?r.data:"";s(x),n(!0)})},[]);function d(r){const x=r.trim().replace(/\/$/,"");if(x.length>0&&!$t.test(x+"/")){u("Expected format: https://github.com/owner/repo");return}u(null),C({type:"SETTINGS_SET",key:re,value:x})}return a?e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"text-xs text-slate-500",children:'Paste your repository URL to enable "File GitHub issue" buttons in the Delta view. Issues are opened in a new tab with title and body pre-filled — you confirm and submit on GitHub.'}),e.jsx("input",{type:"url",value:t,onChange:r=>s(r.target.value),onBlur:r=>d(r.target.value),placeholder:"https://github.com/owner/repo",className:"w-full text-xs font-mono border border-slate-300 rounded px-2 py-1.5"}),l&&e.jsx("p",{className:"text-[11px] text-rose-600",children:l})]}):e.jsx("div",{className:"text-xs text-slate-500",children:"Loading…"})}const oe="jiraInstanceUrl",Ot=/^https:\/\/[^/]+\.atlassian\.net\/?$/;function Bt(){const[t,s]=c.useState(""),[a,n]=c.useState(!1),[l,u]=c.useState(null);c.useEffect(()=>{w({type:"SETTINGS_GET",key:oe}).then(r=>{const x=typeof r.data=="string"?r.data:"";s(x),n(!0)})},[]);function d(r){const x=r.trim().replace(/\/$/,"");if(x.length>0&&!Ot.test(x+"/")){u("Expected format: https://your-team.atlassian.net");return}u(null),C({type:"SETTINGS_SET",key:oe,value:x})}return a?e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"text-xs text-slate-500",children:'Paste your Jira Cloud instance URL to enable "File Jira issue" in the Delta view. Issues open in a new tab pre-filled — you confirm and submit on Jira.'}),e.jsx("input",{type:"url",value:t,onChange:r=>s(r.target.value),onBlur:r=>d(r.target.value),placeholder:"https://your-team.atlassian.net",className:"w-full text-xs font-mono border border-slate-300 rounded px-2 py-1.5"}),l&&e.jsx("p",{className:"text-[11px] text-rose-600",children:l})]}):e.jsx("div",{className:"text-xs text-slate-500",children:"Loading…"})}const le="cloudSyncEnabled",ie="cloudSyncEndpoint",Kt="https://api.wcagcheckr.com";function Vt(){const[t,s]=c.useState(!1),[a,n]=c.useState(Kt),[l,u]=c.useState(!1),[d,r]=c.useState(!1),[x,o]=c.useState(null);c.useEffect(()=>{Promise.all([w({type:"SETTINGS_GET",key:le}),w({type:"SETTINGS_GET",key:ie})]).then(([y,N])=>{s(y.data===!0),typeof N.data=="string"&&N.data&&n(N.data),u(!0)})},[]);function m(y){s(y),C({type:"SETTINGS_SET",key:le,value:y})}function p(y){const N=y.trim().replace(/\/$/,"");n(N),C({type:"SETTINGS_SET",key:ie,value:N})}async function h(){r(!0),o(null);try{const y="To pull baselines now, reload the extension at chrome://extensions. The service worker pulls on every start.";o(y),R.polite(y)}finally{r(!1)}}return l?e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-xs text-slate-500",children:"Sync baselines across devices via your license. Disabled by default — local-first storage means the extension works fully offline. Cloud is mirror, not source of truth on the local machine."}),e.jsxs("label",{className:"flex items-center gap-2 text-xs",children:[e.jsx("input",{type:"checkbox",checked:t,onChange:y=>m(y.target.checked)}),e.jsx("span",{children:"Enable cloud sync"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"text-xs text-slate-500 block mb-1",children:"Sync endpoint"}),e.jsx("input",{type:"url",value:a,onChange:y=>n(y.target.value),onBlur:y=>p(y.target.value),disabled:!t,className:"w-full text-xs font-mono border border-slate-300 rounded px-2 py-1.5 disabled:bg-slate-50 disabled:text-slate-500"}),e.jsx("p",{className:"text-[11px] text-slate-500 mt-1",children:"Default points at the WCAG Auditor cloud-sync VPS. Replace with your own server URL if you self-host the baseline endpoints."})]}),e.jsx("button",{type:"button",onClick:h,disabled:!t||d,className:"text-xs px-3 py-1 border border-slate-300 rounded hover:bg-slate-50 disabled:opacity-50",children:d?"Pulling…":"Pull now"}),x&&e.jsx("p",{className:"text-[11px] text-slate-500",children:x})]}):e.jsx("div",{className:"text-xs text-slate-500",children:"Loading…"})}const ce="runsPerState";function Ht(){const[t,s]=c.useState(1),[a,n]=c.useState(!1);c.useEffect(()=>{w({type:"SETTINGS_GET",key:ce}).then(u=>{const d=typeof u.data=="number"?u.data:1;s(d),n(!0)})},[]);function l(u){s(u),C({type:"SETTINGS_SET",key:ce,value:u})}return a?e.jsxs("div",{className:"space-y-2",children:[e.jsxs("p",{className:"text-xs text-slate-500",children:["Real-world pages are non-deterministic — animations, third-party scripts, lazy-loaded content can produce different violation counts on identical re-runs. Higher run counts let the auditor classify violations as ",e.jsx("strong",{children:"stable"})," (seen in every run) vs"," ",e.jsx("strong",{children:"flaky"})," (only some runs). Tradeoff: each step costs an extra audit pass."]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("label",{htmlFor:"reliability-runs-per-state",className:"text-xs text-slate-700",children:"Audit runs per state:"}),e.jsxs("select",{id:"reliability-runs-per-state",value:t,onChange:u=>l(Number(u.target.value)),className:"text-xs border border-slate-300 rounded px-2 py-1",children:[e.jsx("option",{value:1,children:"1 (fastest)"}),e.jsx("option",{value:2,children:"2 (balanced)"}),e.jsx("option",{value:3,children:"3 (most reliable)"})]})]})]}):e.jsx("div",{className:"text-xs text-slate-500",children:"Loading…"})}function Jt(){const[t,s]=c.useState("owner"),[a,n]=c.useState(!1);c.useEffect(()=>{w({type:"SETTINGS_GET",key:"userMode"}).then(u=>{const d=u.data;(d==="owner"||d==="dev")&&s(d),n(!0)})},[]);function l(u){s(u),C({type:"SETTINGS_SET",key:"userMode",value:u})}return a?e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-xs text-slate-500",children:"Switches the side panel between two surfaces. Owner mode is simpler and avoids jargon; developer mode shows the full feature set (visualizers, baselines, exports, AI fixit prompt)."}),e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsx(de,{active:t==="owner",icon:"🏪",label:"Site owner",desc:"Plain-language results, whole-site scan, send-to-developer report.",onClick:()=>l("owner")}),e.jsx(de,{active:t==="dev",icon:"⚙️",label:"Developer / agency",desc:"Full feature surface — visualizers, exports, baselines, IGT, AI fixit prompt.",onClick:()=>l("dev")})]})]}):e.jsx("div",{className:"text-xs text-slate-500",children:"Loading…"})}function de({active:t,icon:s,label:a,desc:n,onClick:l}){return e.jsx("button",{type:"button",onClick:l,className:`text-left rounded p-3 border transition-colors ${t?"border-brand-500 bg-brand-50 ring-2 ring-brand-500/30":"border-slate-300 bg-white hover:border-slate-400 hover:bg-slate-50"}`,children:e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx("span",{className:"text-lg","aria-hidden":"true",children:s}),e.jsxs("div",{children:[e.jsx("div",{className:"font-medium text-xs",children:a}),e.jsx("div",{className:"text-[11px] text-slate-500 leading-snug mt-0.5",children:n})]})]})})}const Y="aiConfig";function Yt(){const[t,s]=c.useState(V),[a,n]=c.useState(!1),[l,u]=c.useState(!1),[d,r]=c.useState(null),[x,o]=c.useState(null),[m,p]=c.useState(!1);async function h(){r(await He(10))}c.useEffect(()=>{h()},[]);async function y(){p(!0),o(null);try{if(!t.apiKey){o({ok:!1,message:"No API key entered yet."});return}const i=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"x-api-key":t.apiKey,"anthropic-version":"2023-06-01","content-type":"application/json","anthropic-dangerous-direct-browser-access":"true"},body:JSON.stringify({model:t.model||"claude-haiku-4-5",max_tokens:5,messages:[{role:"user",content:"ok"}]})}),f=await i.json().catch(()=>null);if(!f){o({ok:!1,message:`Provider returned no JSON (HTTP ${i.status}).`});return}if("error"in f){o({ok:!1,message:`${f.error.type}: ${f.error.message}`});return}o({ok:!0,message:`Authenticated, account funded. Test call used ${f.usage.input_tokens}+${f.usage.output_tokens} tokens (~$0.0001).`})}catch(i){o({ok:!1,message:i instanceof Error?i.message:String(i)})}finally{p(!1)}}async function N(){typeof confirm=="function"&&!confirm("Clear the local AI usage log? Provider-side billing records are unaffected.")||(await Je(),await h())}c.useEffect(()=>{w({type:"SETTINGS_GET",key:Y}).then(i=>{const f=i.data;f&&s({...V,...f,enabledChecks:{...V.enabledChecks,...f.enabledChecks??{}}}),n(!0)})},[]);function v(i){const f={...t,...i};s(f),C({type:"SETTINGS_SET",key:Y,value:f})}function b(i){const f={...t,enabledChecks:{...t.enabledChecks,...i}};s(f),C({type:"SETTINGS_SET",key:Y,value:f})}return a?e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-xs text-slate-600 leading-snug",children:"Adds vision-verified alt-text checking, heading-quality judgment, sensory-language detection, and ARIA semantic verification to every audit. Each check uses a small AI call against your configured provider; per-audit cost is hard-capped to prevent surprise bills."}),e.jsxs("label",{className:"flex items-center gap-2 text-xs",children:[e.jsx("input",{type:"checkbox",checked:t.enabled,onChange:i=>v({enabled:i.target.checked})}),e.jsx("span",{children:e.jsx("strong",{children:"Enable AI augmentation"})})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"ai-provider",className:"block text-xs text-slate-500 mb-1",children:"Provider"}),e.jsxs("select",{id:"ai-provider",value:t.provider,onChange:i=>v({provider:i.target.value}),disabled:!t.enabled,className:"text-xs border border-slate-300 rounded px-2 py-1.5",children:[e.jsx("option",{value:"anthropic",children:"Anthropic (Claude)"}),e.jsx("option",{value:"openai",children:"OpenAI (GPT) — coming soon"}),e.jsx("option",{value:"gemini",children:"Google (Gemini) — coming soon"})]})]}),e.jsxs("div",{children:[e.jsxs("label",{htmlFor:"ai-key",className:"block text-xs text-slate-500 mb-1",children:["API key",e.jsx("span",{className:"text-[10px] text-slate-400 ml-2",children:"stored locally; never sent anywhere except your chosen provider"})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("input",{id:"ai-key",type:l?"text":"password",value:t.apiKey,onChange:i=>v({apiKey:i.target.value.trim()}),disabled:!t.enabled,placeholder:"sk-ant-…",className:"flex-1 text-xs font-mono border border-slate-300 rounded px-2 py-1.5"}),e.jsx("button",{type:"button",onClick:()=>u(!l),className:"text-[11px] px-2 border border-slate-300 rounded hover:bg-slate-50",children:l?"Hide":"Show"})]})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"ai-model",className:"block text-xs text-slate-500 mb-1",children:"Model override (optional)"}),e.jsx("input",{id:"ai-model",type:"text",value:t.model??"",onChange:i=>v({model:i.target.value.trim()}),disabled:!t.enabled,placeholder:"claude-3-5-sonnet-20241022 (default)",className:"w-full text-xs font-mono border border-slate-300 rounded px-2 py-1.5"}),e.jsx("p",{className:"text-[10px] text-slate-500 mt-1",children:"Leave blank for provider default. Use a haiku-class model for cheap-fast checks."})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"ai-cap",className:"block text-xs text-slate-500 mb-1",children:"Per-audit cost cap (USD)"}),e.jsx("input",{id:"ai-cap",type:"number",step:.5,min:0,value:t.costCapUsd,onChange:i=>v({costCapUsd:Math.max(0,parseFloat(i.target.value)||0)}),disabled:!t.enabled,className:"w-32 text-xs border border-slate-300 rounded px-2 py-1.5"}),e.jsx("p",{className:"text-[10px] text-slate-500 mt-1",children:"Hard ceiling — once exceeded, remaining AI checks for the audit are skipped."})]}),e.jsxs("fieldset",{className:"border border-slate-200 rounded p-3",children:[e.jsx("legend",{className:"text-xs font-medium px-1",children:"Checks to run"}),e.jsxs("div",{className:"space-y-1.5 text-xs",children:[e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.altText,onChange:i=>b({altText:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Alt text"})," — verify every image's alt accurately describes it (vision call, ~$0.005 each)"]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.headings,onChange:i=>b({headings:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Headings"})," — judge whether headings describe their sections (text-only, cheap)"]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.sensory,onChange:i=>b({sensory:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Sensory language"}),' — flag instructions like "click the round button" (text-only, cheap)']})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.aria,onChange:i=>b({aria:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"ARIA semantics"})," — verify role= attributes match element behavior (more expensive; opt-in)"]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.imageOfText,onChange:i=>b({imageOfText:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Images of text"})," (WCAG 1.4.5) — flag banner / share-card / infographic images that bake text into the raster (vision; capped to ",t.imageOfTextMaxImages," largest images / audit)"]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.colorOnlyMeaning,onChange:i=>b({colorOnlyMeaning:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Color-only meaning"})," (WCAG 1.4.1) — flag status indicators / icons / instructions where only color conveys meaning (vision call)"]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.genericLinkText,onChange:i=>b({genericLinkText:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Generic link text"}),` (WCAG 2.4.4) — flag "click here"-style links where surrounding context doesn't clarify the destination (text-only, cheap)`]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.wallOfText,onChange:i=>b({wallOfText:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Wall of text"})," (WCAG 3.1.5) — flag long-form content with no subheadings, lists, or paragraph breaks (text-only, cheap)"]})]}),e.jsxs("label",{className:"flex items-center gap-2",children:[e.jsx("input",{type:"checkbox",checked:t.enabledChecks.languageMismatch,onChange:i=>b({languageMismatch:i.target.checked}),disabled:!t.enabled}),e.jsxs("span",{children:[e.jsx("strong",{children:"Language mismatch"})," (WCAG 3.1.1 / 3.1.2) — flag content that doesn't match the declared ",e.jsx("code",{children:"lang"})," attribute (text-only, cheap)"]})]})]})]}),e.jsxs("fieldset",{className:"border border-slate-200 rounded p-3",children:[e.jsx("legend",{className:"text-xs font-medium px-1",children:"Connection test"}),e.jsx("p",{className:"text-[11px] text-slate-500 mb-2",children:"Fires a 5-token call to your provider to confirm the key authenticates and the account is funded. Costs ≈ $0.0001."}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{type:"button",onClick:()=>void y(),disabled:!t.apiKey||m,className:"text-xs px-3 py-1 border border-slate-300 rounded hover:bg-slate-50 disabled:opacity-50",children:m?"Testing…":"Test connection"}),x&&e.jsxs("span",{className:`text-[11px] ${x.ok?"text-emerald-700":"text-rose-700"}`,children:[x.ok?"✓ ":"✗ ",x.message]})]})]}),e.jsxs("fieldset",{className:"border border-slate-200 rounded p-3",children:[e.jsx("legend",{className:"text-xs font-medium px-1",children:"Local usage"}),e.jsxs("p",{className:"text-[11px] text-slate-500 mb-2",children:["Tracked locally per audit. Providers don't expose live balance via API, so check"," ",e.jsx("a",{href:"https://console.anthropic.com/settings/billing",target:"_blank",rel:"noopener noreferrer",className:"text-brand-700 underline",children:"console.anthropic.com → Plans & Billing"})," ","for your real balance."]}),d&&d.totalAudits===0&&e.jsx("p",{className:"text-[11px] text-slate-500 italic",children:"No AI-augmented audits recorded yet."}),d&&d.totalAudits>0&&e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"grid grid-cols-3 gap-2 text-xs",children:[e.jsxs("div",{className:"border border-slate-200 rounded p-2",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wide text-slate-500",children:"All-time spent"}),e.jsxs("div",{className:"font-semibold text-slate-800",children:["$",d.totalSpentUsd.toFixed(4)]}),e.jsxs("div",{className:"text-[10px] text-slate-500",children:[d.totalAudits," audit",d.totalAudits===1?"":"s"]})]}),e.jsxs("div",{className:"border border-slate-200 rounded p-2",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wide text-slate-500",children:"This month"}),e.jsxs("div",{className:"font-semibold text-slate-800",children:["$",d.spentThisMonthUsd.toFixed(4)]}),e.jsxs("div",{className:"text-[10px] text-slate-500",children:[d.auditsThisMonth," audit",d.auditsThisMonth===1?"":"s"]})]}),e.jsxs("div",{className:"border border-slate-200 rounded p-2",children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wide text-slate-500",children:"Avg / audit"}),e.jsx("div",{className:"font-semibold text-slate-800",children:d.averageCostUsd!=null?`$${d.averageCostUsd.toFixed(4)}`:"—"})]})]}),e.jsxs("details",{children:[e.jsx("summary",{className:"text-[11px] cursor-pointer text-slate-600",children:"By check type"}),e.jsx("ul",{className:"mt-1 text-[11px] text-slate-700",children:Object.entries(d.byCheck).map(([i,f])=>e.jsxs("li",{className:"flex items-center justify-between py-0.5",children:[e.jsx("code",{className:"font-mono",children:i}),e.jsxs("span",{children:["$",(f??0).toFixed(4)]})]},i))})]}),e.jsxs("details",{children:[e.jsx("summary",{className:"text-[11px] cursor-pointer text-slate-600",children:"Recent audits (last 10)"}),e.jsxs("table",{className:"mt-1 w-full text-[11px]",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"text-slate-500 text-left",children:[e.jsx("th",{className:"py-0.5 pr-2 font-medium",children:"When"}),e.jsx("th",{className:"py-0.5 pr-2 font-medium",children:"Target"}),e.jsx("th",{className:"py-0.5 pr-2 font-medium text-right",children:"Cost"}),e.jsx("th",{className:"py-0.5 font-medium text-right",children:"Findings"})]})}),e.jsx("tbody",{children:d.recent.map((i,f)=>e.jsxs("tr",{className:"border-t border-slate-100",children:[e.jsx("td",{className:"py-0.5 pr-2 text-slate-600",children:new Date(i.at).toLocaleDateString()}),e.jsx("td",{className:"py-0.5 pr-2 text-slate-700 truncate max-w-[180px]",title:i.pageUrl,children:i.pageUrl}),e.jsxs("td",{className:"py-0.5 pr-2 text-right font-mono",children:["$",i.totalCostUsd.toFixed(4),i.capExceeded&&e.jsx("span",{className:"text-amber-700 ml-1",title:"Cost cap exceeded",children:"⚠"}),i.failureReason&&e.jsx("span",{className:"text-red-700 ml-1",title:`AI failure: ${i.failureReason}`,children:"✗"})]}),e.jsx("td",{className:"py-0.5 text-right",children:i.findingsCount})]},f))})]})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx("button",{type:"button",onClick:()=>void N(),className:"text-[11px] text-slate-500 hover:text-rose-600 underline-offset-2 hover:underline",children:"Clear local usage log"})})]})]})]}):e.jsx("div",{className:"text-xs text-slate-500",children:"Loading…"})}function Wt(){const[t,s]=c.useState(null),[a,n]=c.useState("trial");c.useEffect(()=>{w({type:"SETTINGS_GET_ALL"}).then(o=>{const m=o.data??{};s({stateMatrix:m.stateMatrix??ee,componentIdAliases:m.componentIdAliases??{}})}),w({type:"TIER_GET"}).then(o=>n(o.tier))},[]);function l(o){t&&(s({...t,stateMatrix:o}),C({type:"SETTINGS_SET",key:"stateMatrix",value:o}))}function u(o){if(!t)return;const m=new Set(o.map(h=>h.id)),p=t.stateMatrix.breakpoints.filter(h=>m.has(h));l({...t.stateMatrix,breakpointPresets:o,breakpoints:p.length>0?p:o[0]?[o[0].id]:[]})}function d(o){t&&(s({...t,componentIdAliases:o}),C({type:"SETTINGS_SET",key:"componentIdAliases",value:o}))}function r(){t&&(typeof confirm=="function"&&!confirm("Reset state matrix to defaults?")||l(ee))}if(!t)return e.jsx("div",{className:"p-8 text-sm text-slate-500",children:"Loading…"});const x=a==="free";return e.jsxs("div",{className:"max-w-2xl mx-auto p-3 sm:p-6 space-y-6 text-sm",children:[e.jsxs("header",{className:"flex items-start justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-xl font-semibold",children:"Settings"}),e.jsx("p",{className:"text-xs text-slate-500 mt-1",children:"Changes are saved automatically. Close this tab to return to the audit panel."})]}),e.jsx("button",{type:"button",onClick:()=>{window.close()},className:"text-xs px-3 py-1.5 bg-brand-500 text-white rounded hover:bg-brand-600 shrink-0",children:"Done"})]}),e.jsx("main",{"aria-label":"Settings",children:e.jsxs(ct,{defaultValue:"audit",className:"space-y-4",children:[e.jsx(dt,{className:"flex flex-wrap gap-1 border-b border-slate-200","aria-label":"Settings categories",children:[{value:"audit",label:"Audit"},{value:"integrations",label:"Integrations"},{value:"account",label:"Account"},{value:"advanced",label:"Advanced"}].map(o=>e.jsx(ut,{value:o.value,className:"text-xs px-3 py-1.5 text-slate-600 hover:text-slate-900 data-[state=active]:text-brand-700 data-[state=active]:border-b-2 data-[state=active]:border-brand-500 -mb-px",children:o.label},o.value))}),e.jsxs(G,{value:"audit",className:"space-y-8 pt-2",children:[e.jsxs("section",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"State matrix"}),e.jsx("button",{onClick:r,className:"text-xs text-slate-500 hover:text-slate-900",children:"Reset to defaults"})]}),e.jsx("p",{className:"text-xs text-slate-500",children:"Which states each audit covers. Larger matrix = more coverage but slower runs."}),x&&e.jsx("div",{className:"text-xs bg-amber-50 border border-amber-200 rounded p-2 text-amber-900",children:"Free tier audits the default state only — your matrix selections below will be ignored until you upgrade."}),e.jsx(ht,{value:t.stateMatrix,onChange:l})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Breakpoint presets"}),e.jsx("p",{className:"text-xs text-slate-500",children:"Customize widths/heights and add your own breakpoints. Toggle which ones audit under in the State matrix section above."}),e.jsx(Lt,{presets:t.stateMatrix.breakpointPresets,onChange:u})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Audit reliability"}),e.jsx(Ht,{})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Disabled rules"}),e.jsx(Dt,{})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Component identity aliases"}),e.jsx("p",{className:"text-xs text-slate-500",children:"Map two component IDs to the same canonical ID — useful when the same component is identified differently across pages."}),e.jsx(bt,{value:t.componentIdAliases,onChange:d})]})]}),e.jsxs(G,{value:"integrations",className:"space-y-8 pt-2",children:[e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"GitHub issue tracker"}),e.jsx(Gt,{})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Jira issue tracker"}),e.jsx(Bt,{})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Cloud sync (baselines)"}),e.jsx(Vt,{})]})]}),e.jsxs(G,{value:"account",className:"space-y-8 pt-2",children:[e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"User mode"}),e.jsx(Jt,{})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"License"}),e.jsx(It,{tier:a,onTierChange:n})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Crash reporting"}),e.jsx(Mt,{})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"AI augmentation"}),e.jsx(Yt,{})]})]}),e.jsxs(G,{value:"advanced",className:"space-y-8 pt-2",children:[e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Report download behavior"}),e.jsx("p",{className:"text-xs text-slate-500",children:"Defense / evidence bundles default to a confirmation prompt before downloading. Power users who export many bundles can skip the prompt here."}),e.jsx(Ut,{})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Import / export"}),e.jsx("p",{className:"text-xs text-slate-500",children:"Share matrix + alias config with your team, or back up before major changes."}),e.jsx(Pt,{stateMatrix:t.stateMatrix,componentIdAliases:t.componentIdAliases,onImport:o=>s(o)})]})]})]})}),e.jsx(De,{})]})}const Re=document.getElementById("root");if(!Re)throw new Error("options: #root not found");$e(Re).render(e.jsx(P.StrictMode,{children:e.jsx(Wt,{})}));
@@ -0,0 +1 @@
1
+ import{c as t,j as r,R as e}from"./styles-kHMb1Lda.js";import{E as s,A as n}from"./ErrorBoundary-BPz4qckm.js";import{i as a}from"./crash-reporter-wxu43qbG.js";a("side-panel");const o=document.getElementById("root");if(!o)throw new Error("side-panel: #root not found");t(o).render(r.jsx(e.StrictMode,{children:r.jsx(s,{children:r.jsx(n,{})})}));
@@ -0,0 +1 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))i(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&i(o)}).observe(document,{childList:!0,subtree:!0});function s(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerPolicy&&(r.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?r.credentials="include":e.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(e){if(e.ep)return;e.ep=!0;const r=s(e);fetch(e.href,r)}})();
@@ -0,0 +1 @@
1
+ import"./modulepreload-polyfill-B5Qt9EMX.js";import"./main-CqDdt0Iq.js";import"./styles-kHMb1Lda.js";import"./_commonjsHelpers-Cpj98o6Y.js";import"./crash-reporter-wxu43qbG.js";import"./state-DnzwwNxZ.js";import"./ai-usage-log-DFkwAfmW.js";
@@ -0,0 +1 @@
1
+ const h="modulepreload",E=function(i){return"/"+i},a={},y=function(u,s,v){let c=Promise.resolve();if(s&&s.length>0){document.getElementsByTagName("link");const e=document.querySelector("meta[property=csp-nonce]"),t=(e==null?void 0:e.nonce)||(e==null?void 0:e.getAttribute("nonce"));c=Promise.allSettled(s.map(r=>{if(r=E(r),r in a)return;a[r]=!0;const o=r.endsWith(".css"),d=o?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${r}"]${d}`))return;const n=document.createElement("link");if(n.rel=o?"stylesheet":h,o||(n.as="script"),n.crossOrigin="",n.href=r,t&&n.setAttribute("nonce",t),document.head.appendChild(n),o)return new Promise((f,m)=>{n.addEventListener("load",f),n.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${r}`)))})}))}function l(e){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return c.then(e=>{for(const t of e||[])t.status==="rejected"&&l(t.reason);return u().catch(l)})};export{y as _};
@@ -0,0 +1 @@
1
+ const f=["table","pre","code","svg","iframe","video","canvas",'[role="grid"]','[role="table"]','[role="img"]'];function d(t){for(const n of f)if(t.matches(n)||t.closest(n))return!0;return!1}function s(t){if(t.id)return`#${CSS.escape(t.id)}`;const n=[];let o=t;for(;o&&o.nodeType===1&&n.length<5;){let r=o.tagName.toLowerCase();if(o.classList.length>0){const l=Array.from(o.classList).slice(0,2).map(u=>`.${CSS.escape(u)}`).join("");r+=l}n.unshift(r),o=o.parentElement}return n.join(" > ")}function h(t,n=document.body){const o=document.documentElement.scrollWidth,r=[],l=[];o>t+1&&r.push({kind:"document-overflow",selector:"html",overflowPx:o-t,outerHtml:""});const u=n.querySelectorAll("*");for(const e of Array.from(u)){const i=window.getComputedStyle(e);if(i.display==="none"||i.visibility==="hidden")continue;const c=e.getBoundingClientRect();if(c.width<=0||c.height<=0)continue;if(c.right>t+1){if(d(e)){l.push(s(e));continue}r.push({kind:"element-overflow",selector:s(e),overflowPx:Math.round(c.right-t),outerHtml:e.outerHTML.slice(0,240)});continue}if((i.overflowX==="scroll"||i.overflowX==="auto")&&e.scrollWidth>e.clientWidth+1){if(d(e)){l.push(s(e));continue}r.push({kind:"horizontal-scroll-container",selector:s(e),overflowPx:e.scrollWidth-e.clientWidth,outerHtml:e.outerHTML.slice(0,240)})}}return{viewportWidthPx:t,documentScrollWidthPx:o,findings:r,exceptionsMatched:l}}export{h as analyzeReflow};