@accesslint/core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- let P = /* @__PURE__ */ new WeakMap();
2
- function qe() {
3
- P = /* @__PURE__ */ new WeakMap();
1
+ let U = /* @__PURE__ */ new WeakMap();
2
+ function Le() {
3
+ U = /* @__PURE__ */ new WeakMap();
4
4
  }
5
- function ge(t) {
5
+ function ve(t) {
6
6
  var n;
7
7
  const a = t.tagName.toLowerCase(), e = (n = t.getAttribute("type")) == null ? void 0 : n.toLowerCase();
8
8
  switch (a) {
@@ -117,25 +117,25 @@ function ge(t) {
117
117
  return null;
118
118
  }
119
119
  }
120
- function R(t) {
120
+ function N(t) {
121
121
  var i;
122
- const a = P.get(t);
122
+ const a = U.get(t);
123
123
  if (a !== void 0) return a;
124
- const n = ((i = t.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase()) || null || ge(t);
125
- return P.set(t, n), n;
124
+ const n = ((i = t.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase()) || null || ve(t);
125
+ return U.set(t, n), n;
126
126
  }
127
- let O = /* @__PURE__ */ new WeakMap();
128
- function Le() {
129
- O = /* @__PURE__ */ new WeakMap();
127
+ let B = /* @__PURE__ */ new WeakMap();
128
+ function Te() {
129
+ B = /* @__PURE__ */ new WeakMap();
130
130
  }
131
131
  function v(t) {
132
- const a = O.get(t);
132
+ const a = B.get(t);
133
133
  if (a !== void 0) return a;
134
- const e = Ee(t);
135
- return O.set(t, e), e;
134
+ const e = Re(t);
135
+ return B.set(t, e), e;
136
136
  }
137
- function Ee(t) {
138
- var r, o, s, l, p;
137
+ function Re(t) {
138
+ var o, r, s, l, p;
139
139
  const a = t.getAttribute("aria-labelledby");
140
140
  if (a) {
141
141
  const c = a.split(/\s+/).map((d) => {
@@ -144,7 +144,7 @@ function Ee(t) {
144
144
  }).filter(Boolean);
145
145
  if (c.length) return c.join(" ");
146
146
  }
147
- const e = (r = t.getAttribute("aria-label")) == null ? void 0 : r.trim();
147
+ const e = (o = t.getAttribute("aria-label")) == null ? void 0 : o.trim();
148
148
  if (e) return e;
149
149
  if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) {
150
150
  if (t.id) {
@@ -154,7 +154,7 @@ function Ee(t) {
154
154
  const c = t.closest("label"), d = c ? A(c).trim() : "";
155
155
  if (d) return d;
156
156
  }
157
- const n = (o = t.getAttribute("title")) == null ? void 0 : o.trim();
157
+ const n = (r = t.getAttribute("title")) == null ? void 0 : r.trim();
158
158
  if (n) return n;
159
159
  if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) {
160
160
  const c = (s = t.getAttribute("placeholder")) == null ? void 0 : s.trim();
@@ -181,7 +181,7 @@ function Ee(t) {
181
181
  }
182
182
  return t instanceof HTMLImageElement || t instanceof HTMLAreaElement ? ((l = t.alt) == null ? void 0 : l.trim()) ?? "" : t instanceof HTMLInputElement && t.type === "image" ? ((p = t.alt) == null ? void 0 : p.trim()) ?? "" : "";
183
183
  }
184
- const Te = /* @__PURE__ */ new Set([
184
+ const Ce = /* @__PURE__ */ new Set([
185
185
  "alert",
186
186
  "alertdialog",
187
187
  "application",
@@ -265,29 +265,29 @@ const Te = /* @__PURE__ */ new Set([
265
265
  "treegrid",
266
266
  "treeitem"
267
267
  ]);
268
- function Ce(t) {
268
+ function Ne(t) {
269
269
  const a = t.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "");
270
- return Te.has(a);
270
+ return Ce.has(a);
271
271
  }
272
272
  function q(t) {
273
273
  let a = t;
274
274
  for (; a; ) {
275
- if (fe(a)) return !0;
275
+ if (ye(a)) return !0;
276
276
  a = a.parentElement;
277
277
  }
278
278
  return !1;
279
279
  }
280
- let B = /* @__PURE__ */ new WeakMap();
281
- function Re() {
282
- B = /* @__PURE__ */ new WeakMap();
280
+ let O = /* @__PURE__ */ new WeakMap();
281
+ function Me() {
282
+ O = /* @__PURE__ */ new WeakMap();
283
283
  }
284
284
  function b(t) {
285
- const a = B.get(t);
285
+ const a = O.get(t);
286
286
  if (a !== void 0) return a;
287
287
  let e;
288
- return t.getAttribute("aria-hidden") === "true" || t instanceof HTMLElement && (t.hidden || t.style.display === "none") ? e = !0 : t.parentElement ? e = b(t.parentElement) : e = !1, B.set(t, e), e;
288
+ return t.getAttribute("aria-hidden") === "true" || t instanceof HTMLElement && (t.hidden || t.style.display === "none") ? e = !0 : t.parentElement ? e = b(t.parentElement) : e = !1, O.set(t, e), e;
289
289
  }
290
- function fe(t) {
290
+ function ye(t) {
291
291
  if (t.getAttribute("aria-hidden") === "true" || t instanceof HTMLElement && t.hidden) return !0;
292
292
  if (typeof getComputedStyle == "function") {
293
293
  const a = getComputedStyle(t);
@@ -297,14 +297,14 @@ function fe(t) {
297
297
  return !1;
298
298
  }
299
299
  function A(t) {
300
- var e, n, i, r, o;
300
+ var e, n, i, o, r;
301
301
  let a = "";
302
302
  for (const s of t.childNodes)
303
303
  if (s.nodeType === 3)
304
304
  a += s.textContent ?? "";
305
305
  else if (s.nodeType === 1) {
306
306
  const l = s;
307
- if (!fe(l)) {
307
+ if (!ye(l)) {
308
308
  const p = (e = l.tagName) == null ? void 0 : e.toLowerCase();
309
309
  if (p === "img" || p === "area") {
310
310
  const c = l.getAttribute("aria-labelledby");
@@ -320,26 +320,26 @@ function A(t) {
320
320
  }
321
321
  a += ((n = l.getAttribute("aria-label")) == null ? void 0 : n.trim()) ?? l.getAttribute("alt") ?? ((i = l.getAttribute("title")) == null ? void 0 : i.trim()) ?? "";
322
322
  } else if (p === "svg") {
323
- const c = (r = l.getAttribute("aria-label")) == null ? void 0 : r.trim();
323
+ const c = (o = l.getAttribute("aria-label")) == null ? void 0 : o.trim();
324
324
  if (c)
325
325
  a += c;
326
326
  else {
327
327
  const d = l.querySelector("title");
328
328
  d && (a += d.textContent ?? "");
329
329
  }
330
- } else (o = l.getAttribute("aria-label")) != null && o.trim() ? a += l.getAttribute("aria-label").trim() : a += A(l);
330
+ } else (r = l.getAttribute("aria-label")) != null && r.trim() ? a += l.getAttribute("aria-label").trim() : a += A(l);
331
331
  }
332
332
  }
333
333
  return a;
334
334
  }
335
- let U = /* @__PURE__ */ new WeakMap();
336
- function Ne() {
337
- U = /* @__PURE__ */ new WeakMap();
335
+ let V = /* @__PURE__ */ new WeakMap();
336
+ function De() {
337
+ V = /* @__PURE__ */ new WeakMap();
338
338
  }
339
- function Me(t) {
339
+ function $e(t) {
340
340
  return t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
341
341
  }
342
- const $e = [
342
+ const He = [
343
343
  "data-testid",
344
344
  "data-test-id",
345
345
  "data-cy",
@@ -349,18 +349,18 @@ const $e = [
349
349
  "for",
350
350
  "aria-label"
351
351
  ];
352
- function He(t) {
352
+ function Pe(t) {
353
353
  const a = t.tagName.toLowerCase();
354
- for (const n of $e) {
354
+ for (const n of He) {
355
355
  const i = t.getAttribute(n);
356
356
  if (i != null && i.length > 0 && i.length < 100)
357
- return `${a}[${n}="${Me(i)}"]`;
357
+ return `${a}[${n}="${$e(i)}"]`;
358
358
  }
359
359
  const e = t.parentElement;
360
360
  if (e) {
361
361
  let n = 0, i = 0;
362
- for (let r = 0; r < e.children.length; r++)
363
- e.children[r].tagName === t.tagName && (n++, e.children[r] === t && (i = n));
362
+ for (let o = 0; o < e.children.length; o++)
363
+ e.children[o].tagName === t.tagName && (n++, e.children[o] === t && (i = n));
364
364
  if (n > 1)
365
365
  return `${a}:nth-of-type(${i})`;
366
366
  }
@@ -375,11 +375,11 @@ function z(t) {
375
375
  n.unshift(`#${CSS.escape(i.id)}`);
376
376
  break;
377
377
  }
378
- if (n.unshift(He(i)), n.length >= 2) {
379
- const r = n.join(" > ");
378
+ if (n.unshift(Pe(i)), n.length >= 2) {
379
+ const o = n.join(" > ");
380
380
  try {
381
- const o = a.querySelectorAll(r);
382
- if (o.length === 1 && o[0] === t) return r;
381
+ const r = a.querySelectorAll(o);
382
+ if (r.length === 1 && r[0] === t) return o;
383
383
  } catch {
384
384
  }
385
385
  }
@@ -388,17 +388,17 @@ function z(t) {
388
388
  return n.join(" > ");
389
389
  }
390
390
  function m(t) {
391
- var r;
392
- const a = U.get(t);
391
+ var o;
392
+ const a = V.get(t);
393
393
  if (a !== void 0) return a;
394
394
  const e = [];
395
395
  let n = t;
396
396
  for (; n; ) {
397
- const o = n.getRootNode();
398
- if (o instanceof ShadowRoot)
399
- e.unshift({ selector: z(n), delimiter: " >>> " }), n = o.host;
397
+ const r = n.getRootNode();
398
+ if (r instanceof ShadowRoot)
399
+ e.unshift({ selector: z(n), delimiter: " >>> " }), n = r.host;
400
400
  else {
401
- const s = (r = o.defaultView) == null ? void 0 : r.frameElement;
401
+ const s = (o = r.defaultView) == null ? void 0 : o.frameElement;
402
402
  if (s)
403
403
  e.unshift({ selector: z(n), delimiter: " >>>iframe> " }), n = s;
404
404
  else {
@@ -407,39 +407,39 @@ function m(t) {
407
407
  }
408
408
  }
409
409
  }
410
- const i = e.map((o, s) => (s === 0 ? "" : o.delimiter) + o.selector).join("");
411
- return U.set(t, i), i;
410
+ const i = e.map((r, s) => (s === 0 ? "" : r.delimiter) + r.selector).join("");
411
+ return V.set(t, i), i;
412
412
  }
413
- function Dn(t) {
413
+ function Un(t) {
414
414
  const a = [], e = [];
415
415
  let n = t;
416
416
  for (; n; ) {
417
- const r = n.indexOf(" >>>iframe> "), o = n.indexOf(" >>> ");
418
- if (r !== -1 && (o === -1 || r <= o))
419
- a.push(n.slice(0, r).trim()), e.push("iframe"), n = n.slice(r + 12);
420
- else if (o !== -1)
421
- a.push(n.slice(0, o).trim()), e.push("shadow"), n = n.slice(o + 5);
417
+ const o = n.indexOf(" >>>iframe> "), r = n.indexOf(" >>> ");
418
+ if (o !== -1 && (r === -1 || o <= r))
419
+ a.push(n.slice(0, o).trim()), e.push("iframe"), n = n.slice(o + 12);
420
+ else if (r !== -1)
421
+ a.push(n.slice(0, r).trim()), e.push("shadow"), n = n.slice(r + 5);
422
422
  else {
423
423
  a.push(n.trim());
424
424
  break;
425
425
  }
426
426
  }
427
427
  let i = document;
428
- for (let r = 0; r < a.length; r++) {
429
- const o = i.querySelector(a[r]);
430
- if (!o) return null;
431
- if (r < a.length - 1)
432
- if (e[r] === "iframe") {
433
- const s = o.contentDocument;
428
+ for (let o = 0; o < a.length; o++) {
429
+ const r = i.querySelector(a[o]);
430
+ if (!r) return null;
431
+ if (o < a.length - 1)
432
+ if (e[o] === "iframe") {
433
+ const s = r.contentDocument;
434
434
  if (!s) return null;
435
435
  i = s;
436
436
  } else {
437
- const s = o.shadowRoot;
437
+ const s = r.shadowRoot;
438
438
  if (!s) return null;
439
439
  i = s;
440
440
  }
441
441
  else
442
- return o;
442
+ return r;
443
443
  }
444
444
  return null;
445
445
  }
@@ -447,7 +447,7 @@ function u(t) {
447
447
  const a = t.outerHTML;
448
448
  return a.length > 200 ? a.slice(0, 200) + "..." : a;
449
449
  }
450
- const De = /* @__PURE__ */ new Set([
450
+ const ze = /* @__PURE__ */ new Set([
451
451
  "aria-activedescendant",
452
452
  "aria-atomic",
453
453
  "aria-autocomplete",
@@ -501,7 +501,7 @@ const De = /* @__PURE__ */ new Set([
501
501
  "aria-valuemin",
502
502
  "aria-valuenow",
503
503
  "aria-valuetext"
504
- ]), Q = /* @__PURE__ */ new Set([
504
+ ]), ee = /* @__PURE__ */ new Set([
505
505
  "aria-atomic",
506
506
  "aria-busy",
507
507
  "aria-disabled",
@@ -512,7 +512,7 @@ const De = /* @__PURE__ */ new Set([
512
512
  "aria-multiselectable",
513
513
  "aria-readonly",
514
514
  "aria-required"
515
- ]), Z = /* @__PURE__ */ new Set(["aria-checked", "aria-pressed"]), ze = /* @__PURE__ */ new Set([
515
+ ]), te = /* @__PURE__ */ new Set(["aria-checked", "aria-pressed"]), Fe = /* @__PURE__ */ new Set([
516
516
  "aria-colcount",
517
517
  "aria-colindex",
518
518
  "aria-colspan",
@@ -526,7 +526,7 @@ const De = /* @__PURE__ */ new Set([
526
526
  "aria-valuemax",
527
527
  "aria-valuemin",
528
528
  "aria-valuenow"
529
- ]), ee = {
529
+ ]), ae = {
530
530
  "aria-autocomplete": /* @__PURE__ */ new Set(["inline", "list", "both", "none"]),
531
531
  "aria-expanded": /* @__PURE__ */ new Set(["true", "false", "undefined"]),
532
532
  "aria-current": /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"]),
@@ -537,7 +537,7 @@ const De = /* @__PURE__ */ new Set([
537
537
  "aria-orientation": /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]),
538
538
  "aria-relevant": /* @__PURE__ */ new Set(["additions", "all", "removals", "text"]),
539
539
  "aria-sort": /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"])
540
- }, te = /* @__PURE__ */ new Set([
540
+ }, ne = /* @__PURE__ */ new Set([
541
541
  "caption",
542
542
  "code",
543
543
  "deletion",
@@ -582,7 +582,7 @@ const De = /* @__PURE__ */ new Set([
582
582
  u: !0,
583
583
  var: !0,
584
584
  wbr: !0
585
- }, Fe = {
585
+ }, Ue = {
586
586
  alert: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
587
587
  article: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
588
588
  banner: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
@@ -615,26 +615,26 @@ const De = /* @__PURE__ */ new Set([
615
615
  time: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
616
616
  tooltip: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"])
617
617
  };
618
- let E = null, T = null;
619
- function Pe() {
620
- E = null, T = null;
618
+ let T = null, R = null;
619
+ function Be() {
620
+ T = null, R = null;
621
621
  }
622
- function X(t) {
622
+ function J(t) {
623
623
  var i;
624
- if (T && (E == null ? void 0 : E.deref()) === t) return T;
624
+ if (R && (T == null ? void 0 : T.deref()) === t) return R;
625
625
  const a = [], e = [], n = [];
626
- for (const r of t.querySelectorAll("*")) {
627
- let o = !1;
628
- for (const c of r.attributes)
626
+ for (const o of t.querySelectorAll("*")) {
627
+ let r = !1;
628
+ for (const c of o.attributes)
629
629
  if (c.name.startsWith("aria-")) {
630
- o = !0;
630
+ r = !0;
631
631
  break;
632
632
  }
633
- if (!o) continue;
633
+ if (!r) continue;
634
634
  let s, l;
635
- const p = () => (s === void 0 && (s = m(r), l = u(r)), { selector: s, html: l });
636
- for (const c of r.attributes)
637
- if (c.name.startsWith("aria-") && !De.has(c.name)) {
635
+ const p = () => (s === void 0 && (s = m(o), l = u(o)), { selector: s, html: l });
636
+ for (const c of o.attributes)
637
+ if (c.name.startsWith("aria-") && !ze.has(c.name)) {
638
638
  const d = p();
639
639
  a.push({
640
640
  ruleId: "aria-valid-attr",
@@ -645,11 +645,11 @@ function X(t) {
645
645
  });
646
646
  break;
647
647
  }
648
- for (const c of r.attributes) {
648
+ for (const c of o.attributes) {
649
649
  if (!c.name.startsWith("aria-")) continue;
650
650
  const d = c.value.trim();
651
- if (!(d === "" && !Q.has(c.name) && !Z.has(c.name))) {
652
- if (Q.has(c.name)) {
651
+ if (!(d === "" && !ee.has(c.name) && !te.has(c.name))) {
652
+ if (ee.has(c.name)) {
653
653
  if (d !== "true" && d !== "false") {
654
654
  const h = p();
655
655
  e.push({
@@ -660,7 +660,7 @@ function X(t) {
660
660
  message: `${c.name} must be "true" or "false", got "${d}".`
661
661
  });
662
662
  }
663
- } else if (Z.has(c.name)) {
663
+ } else if (te.has(c.name)) {
664
664
  if (d !== "true" && d !== "false" && d !== "mixed") {
665
665
  const h = p();
666
666
  e.push({
@@ -671,7 +671,7 @@ function X(t) {
671
671
  message: `${c.name} must be "true", "false", or "mixed", got "${d}".`
672
672
  });
673
673
  }
674
- } else if (ze.has(c.name)) {
674
+ } else if (Fe.has(c.name)) {
675
675
  if (d === "" || !/^-?\d+$/.test(d)) {
676
676
  const h = p();
677
677
  e.push({
@@ -693,10 +693,10 @@ function X(t) {
693
693
  message: `${c.name} must be a number, got "${d}".`
694
694
  });
695
695
  }
696
- } else if (ee[c.name]) {
696
+ } else if (ae[c.name]) {
697
697
  const h = d.split(/\s+/);
698
698
  for (const g of h)
699
- if (!ee[c.name].has(g)) {
699
+ if (!ae[c.name].has(g)) {
700
700
  const f = p();
701
701
  e.push({
702
702
  ruleId: "aria-valid-attr-value",
@@ -710,10 +710,10 @@ function X(t) {
710
710
  }
711
711
  }
712
712
  }
713
- if (!b(r)) {
714
- const c = (i = r.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase(), d = r.tagName.toLowerCase();
713
+ if (!b(o)) {
714
+ const c = (i = o.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase(), d = o.tagName.toLowerCase();
715
715
  if (!c && We[d]) {
716
- const h = r.hasAttribute("aria-label"), g = r.hasAttribute("aria-labelledby");
716
+ const h = o.hasAttribute("aria-label"), g = o.hasAttribute("aria-labelledby");
717
717
  if (h || g) {
718
718
  const f = p();
719
719
  n.push({
@@ -725,8 +725,8 @@ function X(t) {
725
725
  });
726
726
  }
727
727
  } else if (c) {
728
- if (te.has(c)) {
729
- const g = r.hasAttribute("aria-label"), f = r.hasAttribute("aria-labelledby");
728
+ if (ne.has(c)) {
729
+ const g = o.hasAttribute("aria-label"), f = o.hasAttribute("aria-labelledby");
730
730
  if (g || f) {
731
731
  const y = p();
732
732
  n.push({
@@ -738,11 +738,11 @@ function X(t) {
738
738
  });
739
739
  }
740
740
  }
741
- const h = Fe[c];
741
+ const h = Ue[c];
742
742
  if (h) {
743
- for (const g of r.attributes)
743
+ for (const g of o.attributes)
744
744
  if (g.name.startsWith("aria-") && h.has(g.name)) {
745
- if ((g.name === "aria-label" || g.name === "aria-labelledby") && te.has(c))
745
+ if ((g.name === "aria-label" || g.name === "aria-labelledby") && ne.has(c))
746
746
  continue;
747
747
  const f = p();
748
748
  n.push({
@@ -757,28 +757,28 @@ function X(t) {
757
757
  }
758
758
  }
759
759
  }
760
- return E = new WeakRef(t), T = { validAttr: a, validAttrValue: e, prohibitedAttr: n }, T;
760
+ return T = new WeakRef(t), R = { validAttr: a, validAttrValue: e, prohibitedAttr: n }, R;
761
761
  }
762
- let _ = /* @__PURE__ */ new WeakMap(), V = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap();
762
+ let _ = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap(), Y = /* @__PURE__ */ new WeakMap();
763
763
  function Oe() {
764
- _ = /* @__PURE__ */ new WeakMap(), V = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap();
764
+ _ = /* @__PURE__ */ new WeakMap(), G = /* @__PURE__ */ new WeakMap(), Y = /* @__PURE__ */ new WeakMap();
765
765
  }
766
766
  function w(t) {
767
767
  let a = _.get(t);
768
768
  return a || (a = getComputedStyle(t), _.set(t, a), a);
769
769
  }
770
- function N(t, a, e) {
771
- const [n, i, r] = [t, a, e].map((o) => {
772
- const s = o / 255;
770
+ function M(t, a, e) {
771
+ const [n, i, o] = [t, a, e].map((r) => {
772
+ const s = r / 255;
773
773
  return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
774
774
  });
775
- return 0.2126 * n + 0.7152 * i + 0.0722 * r;
775
+ return 0.2126 * n + 0.7152 * i + 0.0722 * o;
776
776
  }
777
- function ve(t, a) {
777
+ function we(t, a) {
778
778
  const e = Math.max(t, a), n = Math.min(t, a);
779
779
  return (e + 0.05) / (n + 0.05);
780
780
  }
781
- const ae = {
781
+ const ie = {
782
782
  black: [0, 0, 0],
783
783
  white: [255, 255, 255],
784
784
  red: [255, 0, 0],
@@ -798,9 +798,9 @@ const ae = {
798
798
  lime: [0, 255, 0],
799
799
  olive: [128, 128, 0]
800
800
  };
801
- function $(t) {
801
+ function D(t) {
802
802
  const a = t.trim().toLowerCase();
803
- if (ae[a]) return ae[a];
803
+ if (ie[a]) return ie[a];
804
804
  const e = a.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
805
805
  if (e)
806
806
  return [parseInt(e[1] + e[1], 16), parseInt(e[2] + e[2], 16), parseInt(e[3] + e[3], 16)];
@@ -812,18 +812,18 @@ function $(t) {
812
812
  );
813
813
  if (i)
814
814
  return [parseInt(i[1]), parseInt(i[2]), parseInt(i[3])];
815
- const r = t.match(
815
+ const o = t.match(
816
816
  /rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)\s*(?:\/\s*[\d.]+%?)?\s*\)/
817
817
  );
818
- return r ? [parseInt(r[1]), parseInt(r[2]), parseInt(r[3])] : null;
818
+ return o ? [parseInt(o[1]), parseInt(o[2]), parseInt(o[3])] : null;
819
819
  }
820
- function Be(t) {
821
- const a = V.get(t);
820
+ function Ve(t) {
821
+ const a = G.get(t);
822
822
  if (a !== void 0) return a;
823
- const e = Ue(t);
824
- return V.set(t, e), e;
823
+ const e = _e(t);
824
+ return G.set(t, e), e;
825
825
  }
826
- function Ue(t) {
826
+ function _e(t) {
827
827
  let a = t;
828
828
  for (; a; ) {
829
829
  const e = w(a), n = e.backgroundImage;
@@ -833,32 +833,32 @@ function Ue(t) {
833
833
  a = a.parentElement;
834
834
  continue;
835
835
  }
836
- const r = i.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || i.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
837
- if (r && (r[1].endsWith("%") ? parseFloat(r[1]) / 100 : parseFloat(r[1])) < 0.1) {
836
+ const o = i.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || i.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
837
+ if (o && (o[1].endsWith("%") ? parseFloat(o[1]) / 100 : parseFloat(o[1])) < 0.1) {
838
838
  a = a.parentElement;
839
839
  continue;
840
840
  }
841
- return $(i);
841
+ return D(i);
842
842
  }
843
843
  return [255, 255, 255];
844
844
  }
845
- const _e = /* @__PURE__ */ new Set(["IMG", "PICTURE", "VIDEO", "SVG"]);
846
- function Ve(t) {
847
- const a = G.get(t);
845
+ const Ge = /* @__PURE__ */ new Set(["IMG", "PICTURE", "VIDEO", "SVG"]);
846
+ function Ye(t) {
847
+ const a = Y.get(t);
848
848
  if (a !== void 0) return a;
849
- const e = Ge(t);
850
- return G.set(t, e), e;
849
+ const e = Xe(t);
850
+ return Y.set(t, e), e;
851
851
  }
852
- function Ge(t) {
852
+ function Xe(t) {
853
853
  let a = t, e = !1;
854
854
  for (; a; ) {
855
855
  const n = w(a).position;
856
856
  if ((n === "absolute" || n === "fixed") && (e = !0), a !== t && n !== "static") {
857
857
  for (const i of a.children)
858
- if (!(i === t || i.contains(t)) && _e.has(i.tagName)) {
858
+ if (!(i === t || i.contains(t)) && Ge.has(i.tagName)) {
859
859
  if (e) return !0;
860
- const r = w(i).position;
861
- if (r === "absolute" || r === "fixed") return !0;
860
+ const o = w(i).position;
861
+ if (o === "absolute" || o === "fixed") return !0;
862
862
  }
863
863
  if (e) break;
864
864
  }
@@ -866,20 +866,20 @@ function Ge(t) {
866
866
  }
867
867
  return !1;
868
868
  }
869
- function Ye(t) {
869
+ function Ke(t) {
870
870
  const a = parseFloat(t);
871
871
  return t.endsWith("pt") ? a * (4 / 3) : a;
872
872
  }
873
- function Xe(t) {
874
- const a = w(t), e = Ye(a.fontSize), n = parseInt(a.fontWeight) || (a.fontWeight === "bold" ? 700 : 400);
873
+ function Je(t) {
874
+ const a = w(t), e = Ke(a.fontSize), n = parseInt(a.fontWeight) || (a.fontWeight === "bold" ? 700 : 400);
875
875
  return e >= 23.5 || e >= 18.5 && n >= 700;
876
876
  }
877
- const ye = /* @__PURE__ */ new Map();
878
- function zn(t, a) {
879
- ye.set(t, a);
877
+ const $ = /* @__PURE__ */ new Map();
878
+ function Bn(t, a) {
879
+ $.set(t, a), X.delete(t);
880
880
  }
881
- function Ke(t, a) {
882
- const e = ye.get(a);
881
+ function Qe(t, a) {
882
+ const e = $.get(a);
883
883
  return e ? t.map((n) => {
884
884
  const i = e[n.id];
885
885
  return i ? {
@@ -889,8 +889,54 @@ function Ke(t, a) {
889
889
  } : n;
890
890
  }) : t;
891
891
  }
892
- function j(t) {
893
- var r, o;
892
+ const X = /* @__PURE__ */ new Map();
893
+ function Ze(t) {
894
+ return t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
895
+ }
896
+ function et(t) {
897
+ const a = t.split(/\{(\d+)\}/);
898
+ let e = "^";
899
+ for (let n = 0; n < a.length; n++)
900
+ n % 2 === 0 ? e += Ze(a[n]) : e += "(.+?)";
901
+ return e += "$", new RegExp(e);
902
+ }
903
+ function tt(t, a) {
904
+ let e = X.get(t);
905
+ if (e || (e = /* @__PURE__ */ new Map(), X.set(t, e)), e.has(a)) return e.get(a);
906
+ const n = $.get(t);
907
+ if (!n) return;
908
+ const i = n[a];
909
+ if (!(i != null && i.messages))
910
+ return e.set(a, []), [];
911
+ const o = [];
912
+ for (const [r, s] of Object.entries(i.messages))
913
+ o.push({
914
+ regex: et(r),
915
+ translated: s
916
+ });
917
+ return e.set(a, o), o;
918
+ }
919
+ function at(t, a, e) {
920
+ const n = tt(e, t);
921
+ if (!n) return a;
922
+ for (const { regex: i, translated: o } of n) {
923
+ const r = a.match(i);
924
+ if (r)
925
+ return o.replace(/\{(\d+)\}/g, (s, l) => {
926
+ const p = parseInt(l, 10);
927
+ return p + 1 < r.length ? r[p + 1] : `{${l}}`;
928
+ });
929
+ }
930
+ return a;
931
+ }
932
+ function Ae(t, a) {
933
+ return $.has(a) ? t.map((e) => {
934
+ const n = at(e.ruleId, e.message, a);
935
+ return n === e.message ? e : { ...e, message: n };
936
+ }) : t;
937
+ }
938
+ function F(t) {
939
+ var o, r;
894
940
  const a = [], e = t.closest("a");
895
941
  if (e) {
896
942
  const s = e.getAttribute("href");
@@ -899,17 +945,17 @@ function j(t) {
899
945
  const n = t.closest("figure");
900
946
  if (n) {
901
947
  const s = n.querySelector("figcaption");
902
- (r = s == null ? void 0 : s.textContent) != null && r.trim() && a.push(`Figcaption: ${s.textContent.trim().slice(0, 100)}`);
948
+ (o = s == null ? void 0 : s.textContent) != null && o.trim() && a.push(`Figcaption: ${s.textContent.trim().slice(0, 100)}`);
903
949
  }
904
950
  const i = t.parentElement;
905
951
  if (i && i !== e) {
906
- const s = t instanceof HTMLImageElement && t.alt || "", l = (o = i.textContent) == null ? void 0 : o.replace(s, "").trim().slice(0, 100);
952
+ const s = t instanceof HTMLImageElement && t.alt || "", l = (r = i.textContent) == null ? void 0 : r.replace(s, "").trim().slice(0, 100);
907
953
  l && a.push(`Adjacent text: ${l}`);
908
954
  }
909
955
  return a.length > 0 ? a.join(`
910
956
  `) : void 0;
911
957
  }
912
- function ne(t) {
958
+ function oe(t) {
913
959
  let a = t;
914
960
  for (; a; ) {
915
961
  if (a instanceof HTMLElement && a.style.visibility === "hidden") return !0;
@@ -917,7 +963,7 @@ function ne(t) {
917
963
  }
918
964
  return !1;
919
965
  }
920
- const Je = {
966
+ const nt = {
921
967
  id: "img-alt",
922
968
  wcag: ["1.1.1"],
923
969
  level: "A",
@@ -927,11 +973,11 @@ const Je = {
927
973
  run(t) {
928
974
  const a = [];
929
975
  for (const e of t.querySelectorAll("img")) {
930
- if (b(e) || ne(e)) continue;
976
+ if (b(e) || oe(e)) continue;
931
977
  const n = e.getAttribute("role");
932
978
  if (n === "presentation" || n === "none") {
933
- const r = e.getAttribute("tabindex");
934
- if (!r || r === "-1") continue;
979
+ const o = e.getAttribute("tabindex");
980
+ if (!o || o === "-1") continue;
935
981
  }
936
982
  const i = e.getAttribute("alt");
937
983
  if (i !== null && i.trim() === "" && i !== "") {
@@ -941,7 +987,7 @@ const Je = {
941
987
  html: u(e),
942
988
  impact: "critical",
943
989
  message: 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',
944
- context: j(e)
990
+ context: F(e)
945
991
  });
946
992
  continue;
947
993
  }
@@ -951,23 +997,23 @@ const Je = {
951
997
  html: u(e),
952
998
  impact: "critical",
953
999
  message: "Image element missing alt attribute.",
954
- context: j(e)
1000
+ context: F(e)
955
1001
  });
956
1002
  }
957
1003
  for (const e of t.querySelectorAll('[role="img"]:not(img):not(svg)'))
958
- b(e) || ne(e) || v(e) || a.push({
1004
+ b(e) || oe(e) || v(e) || a.push({
959
1005
  ruleId: "img-alt",
960
1006
  selector: m(e),
961
1007
  html: u(e),
962
1008
  impact: "critical",
963
1009
  message: 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',
964
- context: j(e)
1010
+ context: F(e)
965
1011
  });
966
1012
  return a;
967
1013
  }
968
1014
  };
969
- function Qe(t) {
970
- var r, o, s;
1015
+ function it(t) {
1016
+ var o, r, s;
971
1017
  const a = t.getAttribute("aria-labelledby");
972
1018
  if (a) {
973
1019
  const l = a.split(/\s+/).map((p) => {
@@ -976,14 +1022,14 @@ function Qe(t) {
976
1022
  }).filter(Boolean);
977
1023
  if (l.length) return l.join(" ");
978
1024
  }
979
- const e = (r = t.getAttribute("aria-label")) == null ? void 0 : r.trim();
1025
+ const e = (o = t.getAttribute("aria-label")) == null ? void 0 : o.trim();
980
1026
  if (e) return e;
981
1027
  const n = t.querySelector("title");
982
- if ((o = n == null ? void 0 : n.textContent) != null && o.trim()) return n.textContent.trim();
1028
+ if ((r = n == null ? void 0 : n.textContent) != null && r.trim()) return n.textContent.trim();
983
1029
  const i = (s = t.getAttribute("title")) == null ? void 0 : s.trim();
984
1030
  return i || "";
985
1031
  }
986
- const Ze = {
1032
+ const ot = {
987
1033
  id: "svg-img-alt",
988
1034
  wcag: ["1.1.1"],
989
1035
  level: "A",
@@ -994,20 +1040,20 @@ const Ze = {
994
1040
  const a = [], e = 'svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';
995
1041
  for (const n of t.querySelectorAll(e)) {
996
1042
  if (b(n)) continue;
997
- if (!Qe(n)) {
998
- const r = n.getAttribute("role");
1043
+ if (!it(n)) {
1044
+ const o = n.getAttribute("role");
999
1045
  a.push({
1000
1046
  ruleId: "svg-img-alt",
1001
1047
  selector: m(n),
1002
1048
  html: u(n),
1003
1049
  impact: "serious",
1004
- message: `${n.tagName.toLowerCase()} with role='${r}' has no accessible name.`
1050
+ message: `${n.tagName.toLowerCase()} with role='${o}' has no accessible name.`
1005
1051
  });
1006
1052
  }
1007
1053
  }
1008
1054
  return a;
1009
1055
  }
1010
- }, et = {
1056
+ }, rt = {
1011
1057
  id: "input-image-alt",
1012
1058
  wcag: ["1.1.1", "4.1.2"],
1013
1059
  level: "A",
@@ -1026,7 +1072,7 @@ const Ze = {
1026
1072
  });
1027
1073
  return a;
1028
1074
  }
1029
- }, tt = {
1075
+ }, st = {
1030
1076
  id: "image-redundant-alt",
1031
1077
  wcag: [],
1032
1078
  level: "A",
@@ -1040,11 +1086,11 @@ const Ze = {
1040
1086
  for (const n of t.querySelectorAll("img[alt]")) {
1041
1087
  const i = n.getAttribute("alt").trim().toLowerCase();
1042
1088
  if (!i) continue;
1043
- const r = n.closest("a, button");
1044
- if (r) {
1045
- const o = ((e = r.textContent) == null ? void 0 : e.trim().toLowerCase()) || "";
1046
- if (o && o === i) {
1047
- const s = r.tagName.toLowerCase(), l = r.getAttribute("href");
1089
+ const o = n.closest("a, button");
1090
+ if (o) {
1091
+ const r = ((e = o.textContent) == null ? void 0 : e.trim().toLowerCase()) || "";
1092
+ if (r && r === i) {
1093
+ const s = o.tagName.toLowerCase(), l = o.getAttribute("href");
1048
1094
  a.push({
1049
1095
  ruleId: "image-redundant-alt",
1050
1096
  selector: m(n),
@@ -1058,7 +1104,7 @@ const Ze = {
1058
1104
  }
1059
1105
  return a;
1060
1106
  }
1061
- }, at = ["image", "picture", "photo", "graphic", "icon", "img"], nt = {
1107
+ }, lt = ["image", "picture", "photo", "graphic", "icon", "img"], ct = {
1062
1108
  id: "image-alt-redundant-words",
1063
1109
  wcag: [],
1064
1110
  level: "A",
@@ -1071,7 +1117,7 @@ const Ze = {
1071
1117
  for (const e of t.querySelectorAll("img[alt]")) {
1072
1118
  const n = e.getAttribute("alt").toLowerCase();
1073
1119
  if (!n) continue;
1074
- const i = at.filter((r) => n.split(/\s+/).includes(r));
1120
+ const i = lt.filter((o) => n.split(/\s+/).includes(o));
1075
1121
  i.length > 0 && a.push({
1076
1122
  ruleId: "image-alt-redundant-words",
1077
1123
  selector: m(e),
@@ -1083,7 +1129,7 @@ const Ze = {
1083
1129
  }
1084
1130
  return a;
1085
1131
  }
1086
- }, it = {
1132
+ }, dt = {
1087
1133
  id: "area-alt",
1088
1134
  wcag: ["1.1.1", "4.1.2"],
1089
1135
  level: "A",
@@ -1105,22 +1151,22 @@ const Ze = {
1105
1151
  return a;
1106
1152
  }
1107
1153
  };
1108
- function rt(t) {
1109
- var i, r;
1154
+ function ut(t) {
1155
+ var i, o;
1110
1156
  const a = t.getAttribute("aria-labelledby");
1111
1157
  if (a) {
1112
- const o = a.split(/\s+/).map((s) => {
1158
+ const r = a.split(/\s+/).map((s) => {
1113
1159
  var l, p;
1114
1160
  return ((p = (l = t.ownerDocument.getElementById(s)) == null ? void 0 : l.textContent) == null ? void 0 : p.trim()) ?? "";
1115
1161
  }).filter(Boolean);
1116
- if (o.length) return o.join(" ");
1162
+ if (r.length) return r.join(" ");
1117
1163
  }
1118
1164
  const e = (i = t.getAttribute("aria-label")) == null ? void 0 : i.trim();
1119
1165
  if (e) return e;
1120
- const n = (r = t.getAttribute("title")) == null ? void 0 : r.trim();
1166
+ const n = (o = t.getAttribute("title")) == null ? void 0 : o.trim();
1121
1167
  return n || "";
1122
1168
  }
1123
- const ot = {
1169
+ const mt = {
1124
1170
  id: "object-alt",
1125
1171
  wcag: ["1.1.1"],
1126
1172
  level: "A",
@@ -1132,17 +1178,17 @@ const ot = {
1132
1178
  const a = [];
1133
1179
  for (const n of t.querySelectorAll("object")) {
1134
1180
  if (b(n) || n instanceof HTMLElement && n.style.visibility === "hidden") continue;
1135
- let i = n.parentElement, r = !1;
1181
+ let i = n.parentElement, o = !1;
1136
1182
  for (; i; ) {
1137
1183
  if (i instanceof HTMLElement && i.style.visibility === "hidden") {
1138
- r = !0;
1184
+ o = !0;
1139
1185
  break;
1140
1186
  }
1141
1187
  i = i.parentElement;
1142
1188
  }
1143
- if (r || n.getAttribute("role") === "presentation" || n.getAttribute("role") === "none" || rt(n)) continue;
1144
- const o = n.getAttribute("data") || "";
1145
- if (!((n.getAttribute("type") || "").startsWith("image/") || /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(o))) {
1189
+ if (o || n.getAttribute("role") === "presentation" || n.getAttribute("role") === "none" || ut(n)) continue;
1190
+ const r = n.getAttribute("data") || "";
1191
+ if (!((n.getAttribute("type") || "").startsWith("image/") || /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(r))) {
1146
1192
  const p = n.querySelector("img[alt]");
1147
1193
  if (p && ((e = p.getAttribute("alt")) != null && e.trim())) continue;
1148
1194
  }
@@ -1156,7 +1202,7 @@ const ot = {
1156
1202
  }
1157
1203
  return a;
1158
1204
  }
1159
- }, st = {
1205
+ }, pt = {
1160
1206
  id: "role-img-alt",
1161
1207
  wcag: ["1.1.1"],
1162
1208
  level: "A",
@@ -1178,7 +1224,7 @@ const ot = {
1178
1224
  return a;
1179
1225
  }
1180
1226
  };
1181
- function jn(t) {
1227
+ function On(t) {
1182
1228
  if (typeof t != "object" || t === null)
1183
1229
  return "Rule spec must be an object";
1184
1230
  const a = t;
@@ -1208,10 +1254,10 @@ function jn(t) {
1208
1254
  return "Rule must have a wcag array";
1209
1255
  if (typeof a.level != "string" || !["A", "AA"].includes(a.level))
1210
1256
  return "Rule must have level A or AA";
1211
- const i = lt(e);
1257
+ const i = ht(e);
1212
1258
  return i || null;
1213
1259
  }
1214
- function lt(t) {
1260
+ function ht(t) {
1215
1261
  switch (t.type) {
1216
1262
  case "selector-exists":
1217
1263
  return null;
@@ -1237,7 +1283,7 @@ function S(t, a, e) {
1237
1283
  }
1238
1284
  return n;
1239
1285
  }
1240
- function I(t) {
1286
+ function E(t) {
1241
1287
  const a = t.skipAriaHidden !== !1;
1242
1288
  return {
1243
1289
  id: t.id,
@@ -1248,27 +1294,27 @@ function I(t) {
1248
1294
  guidance: t.guidance,
1249
1295
  prompt: t.prompt,
1250
1296
  run(e) {
1251
- var i, r;
1297
+ var i, o;
1252
1298
  const n = [];
1253
1299
  switch (t.check.type) {
1254
1300
  case "selector-exists": {
1255
- for (const o of e.querySelectorAll(t.selector))
1256
- a && b(o) || n.push({
1301
+ for (const r of e.querySelectorAll(t.selector))
1302
+ a && b(r) || n.push({
1257
1303
  ruleId: t.id,
1258
- selector: m(o),
1259
- html: u(o),
1304
+ selector: m(r),
1305
+ html: u(r),
1260
1306
  impact: t.impact,
1261
- message: S(t.message, o, t.check),
1262
- element: o
1307
+ message: S(t.message, r, t.check),
1308
+ element: r
1263
1309
  });
1264
1310
  break;
1265
1311
  }
1266
1312
  case "attribute-value": {
1267
- const { attribute: o, operator: s, value: l } = t.check;
1313
+ const { attribute: r, operator: s, value: l } = t.check;
1268
1314
  for (const p of e.querySelectorAll(t.selector)) {
1269
1315
  if (a && b(p)) continue;
1270
- const c = p.getAttribute(o);
1271
- c !== null && ct(c, s, l) && n.push({
1316
+ const c = p.getAttribute(r);
1317
+ c !== null && bt(c, s, l) && n.push({
1272
1318
  ruleId: t.id,
1273
1319
  selector: m(p),
1274
1320
  html: u(p),
@@ -1280,9 +1326,9 @@ function I(t) {
1280
1326
  break;
1281
1327
  }
1282
1328
  case "attribute-missing": {
1283
- const { attribute: o } = t.check;
1329
+ const { attribute: r } = t.check;
1284
1330
  for (const s of e.querySelectorAll(t.selector))
1285
- a && b(s) || s.hasAttribute(o) || n.push({
1331
+ a && b(s) || s.hasAttribute(r) || n.push({
1286
1332
  ruleId: t.id,
1287
1333
  selector: m(s),
1288
1334
  html: u(s),
@@ -1293,7 +1339,7 @@ function I(t) {
1293
1339
  break;
1294
1340
  }
1295
1341
  case "attribute-regex": {
1296
- const { attribute: o, pattern: s, flags: l, shouldMatch: p } = t.check;
1342
+ const { attribute: r, pattern: s, flags: l, shouldMatch: p } = t.check;
1297
1343
  let c;
1298
1344
  try {
1299
1345
  c = new RegExp(s, l);
@@ -1302,7 +1348,7 @@ function I(t) {
1302
1348
  }
1303
1349
  for (const d of e.querySelectorAll(t.selector)) {
1304
1350
  if (a && b(d)) continue;
1305
- const h = d.getAttribute(o);
1351
+ const h = d.getAttribute(r);
1306
1352
  if (h === null) continue;
1307
1353
  const g = c.test(h);
1308
1354
  p && !g ? n.push({
@@ -1324,9 +1370,9 @@ function I(t) {
1324
1370
  break;
1325
1371
  }
1326
1372
  case "child-required": {
1327
- const { childSelector: o } = t.check;
1373
+ const { childSelector: r } = t.check;
1328
1374
  for (const s of e.querySelectorAll(t.selector))
1329
- a && b(s) || s.querySelector(o) || n.push({
1375
+ a && b(s) || s.querySelector(r) || n.push({
1330
1376
  ruleId: t.id,
1331
1377
  selector: m(s),
1332
1378
  html: u(s),
@@ -1337,7 +1383,7 @@ function I(t) {
1337
1383
  break;
1338
1384
  }
1339
1385
  case "child-invalid": {
1340
- const o = new Set(
1386
+ const r = new Set(
1341
1387
  t.check.allowedChildren.map((l) => l.toLowerCase())
1342
1388
  ), s = t.check.allowedChildRoles ? new Set(t.check.allowedChildRoles.map((l) => l.toLowerCase())) : null;
1343
1389
  for (const l of e.querySelectorAll(t.selector)) {
@@ -1363,8 +1409,8 @@ function I(t) {
1363
1409
  }
1364
1410
  if (!c)
1365
1411
  for (const h of l.children) {
1366
- if (o.has(h.tagName.toLowerCase())) continue;
1367
- const g = (r = h.getAttribute("role")) == null ? void 0 : r.trim().toLowerCase();
1412
+ if (r.has(h.tagName.toLowerCase())) continue;
1413
+ const g = (o = h.getAttribute("role")) == null ? void 0 : o.trim().toLowerCase();
1368
1414
  if (!(g && (s != null && s.has(g))) && !(g === "presentation" || g === "none")) {
1369
1415
  n.push({
1370
1416
  ruleId: t.id,
@@ -1385,7 +1431,7 @@ function I(t) {
1385
1431
  }
1386
1432
  };
1387
1433
  }
1388
- function ct(t, a, e) {
1434
+ function bt(t, a, e) {
1389
1435
  switch (a) {
1390
1436
  case ">":
1391
1437
  return parseFloat(t) > e;
@@ -1403,7 +1449,7 @@ function ct(t, a, e) {
1403
1449
  return !1;
1404
1450
  }
1405
1451
  }
1406
- const dt = {
1452
+ const gt = {
1407
1453
  id: "server-side-image-map",
1408
1454
  selector: "img[ismap], input[type='image'][ismap]",
1409
1455
  check: { type: "selector-exists" },
@@ -1414,7 +1460,7 @@ const dt = {
1414
1460
  level: "A",
1415
1461
  guidance: "Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.",
1416
1462
  prompt: "Explain that the ismap attribute should be removed and the functionality replaced with a client-side <map> element with <area> children, or separate linked images/buttons."
1417
- }, ut = I(dt), mt = [
1463
+ }, ft = E(gt), vt = [
1418
1464
  '[role="checkbox"]',
1419
1465
  '[role="combobox"]',
1420
1466
  '[role="listbox"]',
@@ -1426,13 +1472,13 @@ const dt = {
1426
1472
  '[role="spinbutton"]',
1427
1473
  '[role="switch"]',
1428
1474
  '[role="textbox"]'
1429
- ].join(", "), pt = /* @__PURE__ */ new Set([
1475
+ ].join(", "), yt = /* @__PURE__ */ new Set([
1430
1476
  "checkbox",
1431
1477
  "menuitemcheckbox",
1432
1478
  "menuitemradio",
1433
1479
  "radio",
1434
1480
  "switch"
1435
- ]), ht = /* @__PURE__ */ new Set([
1481
+ ]), wt = /* @__PURE__ */ new Set([
1436
1482
  "combobox",
1437
1483
  "listbox",
1438
1484
  "searchbox",
@@ -1440,10 +1486,10 @@ const dt = {
1440
1486
  "spinbutton",
1441
1487
  "textbox"
1442
1488
  ]);
1443
- function bt(t) {
1444
- var o, s, l, p;
1445
- const a = (o = t.getAttribute("role")) == null ? void 0 : o.trim().toLowerCase();
1446
- if (a && pt.has(a) || (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) && !(a && ht.has(a)))
1489
+ function At(t) {
1490
+ var r, s, l, p;
1491
+ const a = (r = t.getAttribute("role")) == null ? void 0 : r.trim().toLowerCase();
1492
+ if (a && yt.has(a) || (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) && !(a && wt.has(a)))
1447
1493
  return v(t);
1448
1494
  const n = t.getAttribute("aria-labelledby");
1449
1495
  if (n) {
@@ -1469,15 +1515,15 @@ function bt(t) {
1469
1515
  if (d) return d;
1470
1516
  }
1471
1517
  }
1472
- const r = (l = t.getAttribute("title")) == null ? void 0 : l.trim();
1473
- if (r) return r;
1518
+ const o = (l = t.getAttribute("title")) == null ? void 0 : l.trim();
1519
+ if (o) return o;
1474
1520
  if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) {
1475
1521
  const c = (p = t.getAttribute("placeholder")) == null ? void 0 : p.trim();
1476
1522
  if (c) return c;
1477
1523
  }
1478
1524
  return "";
1479
1525
  }
1480
- const gt = {
1526
+ const xt = {
1481
1527
  id: "label",
1482
1528
  wcag: ["4.1.2"],
1483
1529
  level: "A",
@@ -1486,23 +1532,23 @@ const gt = {
1486
1532
  prompt: `This form field has no accessible label. Based on the context (input type, name attribute, placeholder, or surrounding elements), suggest adding a <label for="id"> element with descriptive text, or an aria-label attribute. The label should describe what information the user should enter, not the field type. For example: 'Email address', 'Search', 'Phone number'.`,
1487
1533
  run(t) {
1488
1534
  var i;
1489
- const a = [], n = t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${mt}`);
1490
- for (const r of n) {
1491
- if (b(r) || q(r) || (r instanceof HTMLInputElement || r instanceof HTMLTextAreaElement || r instanceof HTMLSelectElement || r instanceof HTMLButtonElement) && r.disabled || r.closest("fieldset[disabled]") || r.getAttribute("aria-disabled") === "true") continue;
1492
- const o = (i = r.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase();
1493
- if (o === "presentation" || o === "none") continue;
1494
- if (!bt(r)) {
1495
- const l = [], p = r.tagName.toLowerCase(), c = r.getAttribute("type");
1535
+ const a = [], n = t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${vt}`);
1536
+ for (const o of n) {
1537
+ if (b(o) || q(o) || (o instanceof HTMLInputElement || o instanceof HTMLTextAreaElement || o instanceof HTMLSelectElement || o instanceof HTMLButtonElement) && o.disabled || o.closest("fieldset[disabled]") || o.getAttribute("aria-disabled") === "true") continue;
1538
+ const r = (i = o.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase();
1539
+ if (r === "presentation" || r === "none") continue;
1540
+ if (!At(o)) {
1541
+ const l = [], p = o.tagName.toLowerCase(), c = o.getAttribute("type");
1496
1542
  c && p === "input" && l.push(`type: ${c}`);
1497
- const d = r.getAttribute("name");
1543
+ const d = o.getAttribute("name");
1498
1544
  d && l.push(`name: "${d}"`);
1499
- const h = r.getAttribute("placeholder");
1500
- h && l.push(`placeholder: "${h}"`), o && l.push(`role: ${o}`);
1501
- const g = r.getAttribute("id");
1545
+ const h = o.getAttribute("placeholder");
1546
+ h && l.push(`placeholder: "${h}"`), r && l.push(`role: ${r}`);
1547
+ const g = o.getAttribute("id");
1502
1548
  g && l.push(`id: "${g}"`), a.push({
1503
1549
  ruleId: "label",
1504
- selector: m(r),
1505
- html: u(r),
1550
+ selector: m(o),
1551
+ html: u(o),
1506
1552
  impact: "critical",
1507
1553
  message: "Form element has no accessible label.",
1508
1554
  context: l.length > 0 ? l.join(", ") : void 0
@@ -1511,7 +1557,7 @@ const gt = {
1511
1557
  }
1512
1558
  return a;
1513
1559
  }
1514
- }, ft = {
1560
+ }, kt = {
1515
1561
  id: "form-field-multiple-labels",
1516
1562
  wcag: [],
1517
1563
  level: "A",
@@ -1524,15 +1570,15 @@ const gt = {
1524
1570
  for (const n of e) {
1525
1571
  if (b(n) || !n.id) continue;
1526
1572
  const i = t.querySelectorAll(`label[for="${CSS.escape(n.id)}"]`);
1527
- let r = 0, o = n.parentElement;
1528
- for (; o; ) {
1529
- if (o.tagName.toLowerCase() === "label" && !o.hasAttribute("for")) {
1530
- r++;
1573
+ let o = 0, r = n.parentElement;
1574
+ for (; r; ) {
1575
+ if (r.tagName.toLowerCase() === "label" && !r.hasAttribute("for")) {
1576
+ o++;
1531
1577
  break;
1532
1578
  }
1533
- o = o.parentElement;
1579
+ r = r.parentElement;
1534
1580
  }
1535
- const s = i.length + r;
1581
+ const s = i.length + o;
1536
1582
  s > 1 && a.push({
1537
1583
  ruleId: "form-field-multiple-labels",
1538
1584
  selector: m(n),
@@ -1543,7 +1589,7 @@ const gt = {
1543
1589
  }
1544
1590
  return a;
1545
1591
  }
1546
- }, vt = {
1592
+ }, St = {
1547
1593
  id: "select-name",
1548
1594
  wcag: ["4.1.2"],
1549
1595
  level: "A",
@@ -1562,7 +1608,7 @@ const gt = {
1562
1608
  });
1563
1609
  return a;
1564
1610
  }
1565
- }, yt = {
1611
+ }, It = {
1566
1612
  id: "input-button-name",
1567
1613
  wcag: ["4.1.2"],
1568
1614
  level: "A",
@@ -1576,8 +1622,8 @@ const gt = {
1576
1622
  'input[type="submit"], input[type="button"], input[type="reset"]'
1577
1623
  )) {
1578
1624
  if (b(i)) continue;
1579
- const r = (e = i.getAttribute("value")) == null ? void 0 : e.trim(), o = (n = i.getAttribute("type")) == null ? void 0 : n.toLowerCase(), s = (o === "submit" || o === "reset") && !i.hasAttribute("value");
1580
- !r && !s && !v(i) && a.push({
1625
+ const o = (e = i.getAttribute("value")) == null ? void 0 : e.trim(), r = (n = i.getAttribute("type")) == null ? void 0 : n.toLowerCase(), s = (r === "submit" || r === "reset") && !i.hasAttribute("value");
1626
+ !o && !s && !v(i) && a.push({
1581
1627
  ruleId: "input-button-name",
1582
1628
  selector: m(i),
1583
1629
  html: u(i),
@@ -1587,7 +1633,7 @@ const gt = {
1587
1633
  }
1588
1634
  return a;
1589
1635
  }
1590
- }, wt = /* @__PURE__ */ new Set([
1636
+ }, Et = /* @__PURE__ */ new Set([
1591
1637
  "off",
1592
1638
  "on",
1593
1639
  "name",
@@ -1642,7 +1688,7 @@ const gt = {
1642
1688
  "impp",
1643
1689
  "url",
1644
1690
  "photo"
1645
- ]), At = /* @__PURE__ */ new Set([
1691
+ ]), qt = /* @__PURE__ */ new Set([
1646
1692
  "tel",
1647
1693
  "tel-country-code",
1648
1694
  "tel-national",
@@ -1651,18 +1697,18 @@ const gt = {
1651
1697
  "tel-extension",
1652
1698
  "email",
1653
1699
  "impp"
1654
- ]), xt = /* @__PURE__ */ new Set(["home", "work", "mobile", "fax", "pager"]), kt = /* @__PURE__ */ new Set(["shipping", "billing"]), St = /* @__PURE__ */ new Set(["webauthn"]);
1655
- function It(t) {
1700
+ ]), Lt = /* @__PURE__ */ new Set(["home", "work", "mobile", "fax", "pager"]), Tt = /* @__PURE__ */ new Set(["shipping", "billing"]), Rt = /* @__PURE__ */ new Set(["webauthn"]);
1701
+ function Ct(t) {
1656
1702
  const a = t.toLowerCase().split(/\s+/).filter(Boolean);
1657
1703
  if (a.length === 0) return !0;
1658
1704
  let e = 0;
1659
- a[e].startsWith("section-") && e++, e < a.length && kt.has(a[e]) && e++;
1705
+ a[e].startsWith("section-") && e++, e < a.length && Tt.has(a[e]) && e++;
1660
1706
  let n = !1;
1661
- if (e < a.length && xt.has(a[e]) && (n = !0, e++), e >= a.length) return !1;
1707
+ if (e < a.length && Lt.has(a[e]) && (n = !0, e++), e >= a.length) return !1;
1662
1708
  const i = a[e];
1663
- return !wt.has(i) || n && !At.has(i) ? !1 : (e++, e < a.length && St.has(a[e]) && e++, e === a.length);
1709
+ return !Et.has(i) || n && !qt.has(i) ? !1 : (e++, e < a.length && Rt.has(a[e]) && e++, e === a.length);
1664
1710
  }
1665
- const qt = {
1711
+ const Nt = {
1666
1712
  id: "autocomplete-valid",
1667
1713
  wcag: ["1.3.5"],
1668
1714
  level: "AA",
@@ -1674,7 +1720,7 @@ const qt = {
1674
1720
  for (const e of t.querySelectorAll("[autocomplete]")) {
1675
1721
  if (b(e) || e instanceof HTMLElement && e.style.display === "none" || e.disabled || e.getAttribute("aria-disabled") === "true") continue;
1676
1722
  const n = e.getAttribute("autocomplete").trim();
1677
- n && (It(n) || a.push({
1723
+ n && (Ct(n) || a.push({
1678
1724
  ruleId: "autocomplete-valid",
1679
1725
  selector: m(e),
1680
1726
  html: u(e),
@@ -1685,16 +1731,16 @@ const qt = {
1685
1731
  return a;
1686
1732
  }
1687
1733
  };
1688
- function ie(t) {
1734
+ function re(t) {
1689
1735
  return t.toLowerCase().replace(/\s+/g, " ").trim();
1690
1736
  }
1691
- function re(t, a) {
1692
- const e = ie(t), n = ie(a);
1737
+ function se(t, a) {
1738
+ const e = re(t), n = re(a);
1693
1739
  if (!e || !n || e.includes(n) || n.includes(e)) return !0;
1694
- const i = n.split(/\s+/).map((r) => r.replace(/[.,;:!?\u2026]+$/g, "")).filter((r) => r.length > 2);
1695
- return i.length >= 2 && i.filter((o) => e.includes(o)).length / i.length > 0.5;
1740
+ const i = n.split(/\s+/).map((o) => o.replace(/[.,;:!?\u2026]+$/g, "")).filter((o) => o.length > 2);
1741
+ return i.length >= 2 && i.filter((r) => e.includes(r)).length / i.length > 0.5;
1696
1742
  }
1697
- function Y(t) {
1743
+ function K(t) {
1698
1744
  let a = "";
1699
1745
  for (const e of t.childNodes)
1700
1746
  if (e.nodeType === 3)
@@ -1702,13 +1748,13 @@ function Y(t) {
1702
1748
  else if (e.nodeType === 1) {
1703
1749
  const n = e, i = n.tagName.toLowerCase();
1704
1750
  if (i === "style" || i === "script" || i === "svg" || n.getAttribute("aria-hidden") === "true" || n instanceof HTMLElement && n.style.display === "none") continue;
1705
- const r = n.getAttribute("role");
1706
- if (r === "img" || r === "presentation" || r === "none") continue;
1707
- a += Y(n);
1751
+ const o = n.getAttribute("role");
1752
+ if (o === "img" || o === "presentation" || o === "none") continue;
1753
+ a += K(n);
1708
1754
  }
1709
1755
  return a;
1710
1756
  }
1711
- const Lt = {
1757
+ const Mt = {
1712
1758
  id: "label-content-name-mismatch",
1713
1759
  wcag: [],
1714
1760
  level: "A",
@@ -1723,11 +1769,11 @@ const Lt = {
1723
1769
  const n = v(e);
1724
1770
  if (!n) continue;
1725
1771
  let i = "";
1726
- e instanceof HTMLInputElement ? i = e.value || "" : i = Y(e);
1727
- const r = i.trim();
1728
- if (!r || r.length <= 2) continue;
1729
- const o = e.hasAttribute("aria-label"), s = e.hasAttribute("aria-labelledby");
1730
- !o && !s || re(n, i) || a.push({
1772
+ e instanceof HTMLInputElement ? i = e.value || "" : i = K(e);
1773
+ const o = i.trim();
1774
+ if (!o || o.length <= 2) continue;
1775
+ const r = e.hasAttribute("aria-label"), s = e.hasAttribute("aria-labelledby");
1776
+ !r && !s || se(n, i) || a.push({
1731
1777
  ruleId: "label-content-name-mismatch",
1732
1778
  selector: m(e),
1733
1779
  html: u(e),
@@ -1739,23 +1785,23 @@ const Lt = {
1739
1785
  if (b(e) || e instanceof HTMLInputElement && ["hidden", "submit", "button", "image"].includes(e.type)) continue;
1740
1786
  const n = v(e);
1741
1787
  if (!n || !e.hasAttribute("aria-label")) continue;
1742
- const r = e.id;
1743
- let o = "";
1744
- if (r) {
1745
- const s = t.querySelector(`label[for="${CSS.escape(r)}"]`);
1746
- s && (o = Y(s));
1788
+ const o = e.id;
1789
+ let r = "";
1790
+ if (o) {
1791
+ const s = t.querySelector(`label[for="${CSS.escape(o)}"]`);
1792
+ s && (r = K(s));
1747
1793
  }
1748
- o.trim() && (re(n, o) || a.push({
1794
+ r.trim() && (se(n, r) || a.push({
1749
1795
  ruleId: "label-content-name-mismatch",
1750
1796
  selector: m(e),
1751
1797
  html: u(e),
1752
1798
  impact: "serious",
1753
- message: `Accessible name "${n}" does not contain visible label "${o.trim()}".`
1799
+ message: `Accessible name "${n}" does not contain visible label "${r.trim()}".`
1754
1800
  }));
1755
1801
  }
1756
1802
  return a;
1757
1803
  }
1758
- }, Et = {
1804
+ }, Dt = {
1759
1805
  id: "label-title-only",
1760
1806
  wcag: [],
1761
1807
  level: "A",
@@ -1764,7 +1810,7 @@ const Lt = {
1764
1810
  guidance: "The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.",
1765
1811
  prompt: "The title attribute text should be moved to a visible <label> element or aria-label. Show what text to use based on the current title.",
1766
1812
  run(t) {
1767
- var n, i, r, o;
1813
+ var n, i, o, r;
1768
1814
  const a = [], e = t.querySelectorAll(
1769
1815
  'input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select'
1770
1816
  );
@@ -1775,10 +1821,10 @@ const Lt = {
1775
1821
  const h = s.id;
1776
1822
  if (h) {
1777
1823
  const f = s.ownerDocument.querySelector(`label[for="${CSS.escape(h)}"]`);
1778
- (r = f == null ? void 0 : f.textContent) != null && r.trim() && (d = !0);
1824
+ (o = f == null ? void 0 : f.textContent) != null && o.trim() && (d = !0);
1779
1825
  }
1780
1826
  const g = s.closest("label");
1781
- (o = g == null ? void 0 : g.textContent) != null && o.trim() && (d = !0), l && !p && !c && !d && a.push({
1827
+ (r = g == null ? void 0 : g.textContent) != null && r.trim() && (d = !0), l && !p && !c && !d && a.push({
1782
1828
  ruleId: "label-title-only",
1783
1829
  selector: m(s),
1784
1830
  html: u(s),
@@ -1788,7 +1834,7 @@ const Lt = {
1788
1834
  }
1789
1835
  return a;
1790
1836
  }
1791
- }, Tt = {
1837
+ }, $t = {
1792
1838
  id: "tabindex",
1793
1839
  selector: "[tabindex]",
1794
1840
  check: { type: "attribute-value", attribute: "tabindex", operator: ">", value: 0 },
@@ -1800,7 +1846,7 @@ const Lt = {
1800
1846
  tags: ["best-practice"],
1801
1847
  guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",
1802
1848
  prompt: "Change the positive tabindex value to tabindex='0' and rely on DOM order for tab sequence instead."
1803
- }, Ct = I(Tt), Rt = /* @__PURE__ */ new Set([
1849
+ }, Ht = E($t), Pt = /* @__PURE__ */ new Set([
1804
1850
  "div",
1805
1851
  "span",
1806
1852
  "p",
@@ -1827,7 +1873,7 @@ const Lt = {
1827
1873
  "tr",
1828
1874
  "td",
1829
1875
  "th"
1830
- ]), Nt = {
1876
+ ]), zt = {
1831
1877
  id: "focus-order-semantics",
1832
1878
  wcag: [],
1833
1879
  tags: ["best-practice"],
@@ -1839,7 +1885,7 @@ const Lt = {
1839
1885
  const a = [];
1840
1886
  for (const e of t.querySelectorAll('[tabindex="0"]')) {
1841
1887
  const n = e.tagName.toLowerCase();
1842
- if (!Rt.has(n)) continue;
1888
+ if (!Pt.has(n)) continue;
1843
1889
  e.getAttribute("role") || a.push({
1844
1890
  ruleId: "focus-order-semantics",
1845
1891
  selector: m(e),
@@ -1850,7 +1896,7 @@ const Lt = {
1850
1896
  }
1851
1897
  return a;
1852
1898
  }
1853
- }, Mt = /* @__PURE__ */ new Set([
1899
+ }, Ft = /* @__PURE__ */ new Set([
1854
1900
  "a",
1855
1901
  "audio",
1856
1902
  "button",
@@ -1859,7 +1905,7 @@ const Lt = {
1859
1905
  "select",
1860
1906
  "textarea",
1861
1907
  "video"
1862
- ]), $t = /* @__PURE__ */ new Set([
1908
+ ]), jt = /* @__PURE__ */ new Set([
1863
1909
  "button",
1864
1910
  "checkbox",
1865
1911
  "combobox",
@@ -1883,7 +1929,7 @@ const Lt = {
1883
1929
  "tabpanel",
1884
1930
  "textbox",
1885
1931
  "treeitem"
1886
- ]), Ht = {
1932
+ ]), Wt = {
1887
1933
  grid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader"]),
1888
1934
  listbox: /* @__PURE__ */ new Set(["option"]),
1889
1935
  menu: /* @__PURE__ */ new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]),
@@ -1893,26 +1939,26 @@ const Lt = {
1893
1939
  tree: /* @__PURE__ */ new Set(["treeitem"]),
1894
1940
  treegrid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader", "treeitem"])
1895
1941
  };
1896
- function Dt(t, a) {
1897
- var i, r, o;
1898
- const e = (i = t.getAttribute("role")) == null ? void 0 : i.toLowerCase(), n = (r = a.getAttribute("role")) == null ? void 0 : r.toLowerCase();
1899
- return !e || !n ? !1 : ((o = Ht[e]) == null ? void 0 : o.has(n)) ?? !1;
1942
+ function Ut(t, a) {
1943
+ var i, o, r;
1944
+ const e = (i = t.getAttribute("role")) == null ? void 0 : i.toLowerCase(), n = (o = a.getAttribute("role")) == null ? void 0 : o.toLowerCase();
1945
+ return !e || !n ? !1 : ((r = Wt[e]) == null ? void 0 : r.has(n)) ?? !1;
1900
1946
  }
1901
- function zt(t) {
1947
+ function Bt(t) {
1902
1948
  var i;
1903
1949
  const a = t.tagName.toLowerCase();
1904
- if (Mt.has(a))
1950
+ if (Ft.has(a))
1905
1951
  return a === "a" && !t.hasAttribute("href") ? !1 : a === "audio" || a === "video" ? t.hasAttribute("controls") : !(a === "img" && !t.hasAttribute("usemap") || a === "input" && t.type === "hidden" || t.disabled);
1906
1952
  const e = (i = t.getAttribute("role")) == null ? void 0 : i.toLowerCase();
1907
- if (e && $t.has(e)) return !0;
1953
+ if (e && jt.has(e)) return !0;
1908
1954
  const n = t.getAttribute("tabindex");
1909
1955
  return n !== null && n !== "-1" || t.getAttribute("contenteditable") === "true";
1910
1956
  }
1911
- function jt(t) {
1957
+ function Ot(t) {
1912
1958
  const a = t.tagName.toLowerCase();
1913
1959
  return !!(a === "a" && t.hasAttribute("href") || a === "button" && !t.disabled);
1914
1960
  }
1915
- const Wt = {
1961
+ const Vt = {
1916
1962
  id: "nested-interactive",
1917
1963
  wcag: ["4.1.2"],
1918
1964
  level: "A",
@@ -1922,29 +1968,29 @@ const Wt = {
1922
1968
  run(t) {
1923
1969
  const a = [], e = t.body ?? t;
1924
1970
  if (!e) return a;
1925
- const i = (t.body ? t : t.ownerDocument).createTreeWalker(e, NodeFilter.SHOW_ELEMENT), r = [];
1926
- let o = i.currentNode;
1927
- for (; o; ) {
1928
- for (; r.length > 0 && !r[r.length - 1].contains(o); )
1929
- r.pop();
1930
- if (!b(o) && zt(o)) {
1931
- if (r.length > 0) {
1932
- const s = r[r.length - 1];
1933
- Dt(s, o) || a.push({
1971
+ const i = (t.body ? t : t.ownerDocument).createTreeWalker(e, NodeFilter.SHOW_ELEMENT), o = [];
1972
+ let r = i.currentNode;
1973
+ for (; r; ) {
1974
+ for (; o.length > 0 && !o[o.length - 1].contains(r); )
1975
+ o.pop();
1976
+ if (!b(r) && Bt(r)) {
1977
+ if (o.length > 0) {
1978
+ const s = o[o.length - 1];
1979
+ Ut(s, r) || a.push({
1934
1980
  ruleId: "nested-interactive",
1935
- selector: m(o),
1936
- html: u(o),
1981
+ selector: m(r),
1982
+ html: u(r),
1937
1983
  impact: "serious",
1938
- message: `Interactive element <${o.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`
1984
+ message: `Interactive element <${r.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`
1939
1985
  });
1940
1986
  }
1941
- jt(o) && r.push(o);
1987
+ Ot(r) && o.push(r);
1942
1988
  }
1943
- o = i.nextNode();
1989
+ r = i.nextNode();
1944
1990
  }
1945
1991
  return a;
1946
1992
  }
1947
- }, Ft = {
1993
+ }, _t = {
1948
1994
  id: "scrollable-region-focusable",
1949
1995
  wcag: ["2.1.1"],
1950
1996
  level: "A",
@@ -1958,15 +2004,15 @@ const Wt = {
1958
2004
  if (b(n) || !(n instanceof HTMLElement)) continue;
1959
2005
  const i = n.tagName.toLowerCase();
1960
2006
  if (i === "body" || i === "html") continue;
1961
- const r = n.getAttribute("role");
1962
- if (r === "presentation" || r === "none") continue;
1963
- const o = w(n), s = o.overflowX, l = o.overflowY;
2007
+ const o = n.getAttribute("role");
2008
+ if (o === "presentation" || o === "none") continue;
2009
+ const r = w(n), s = r.overflowX, l = r.overflowY;
1964
2010
  if (!(s === "scroll" || s === "auto" || l === "scroll" || l === "auto")) continue;
1965
2011
  if (n.scrollHeight > 0 || n.clientHeight > 0) {
1966
2012
  const g = n.scrollHeight - n.clientHeight, f = n.scrollWidth - n.clientWidth;
1967
2013
  if (g <= 0 && f <= 0 || g < 14 && f < 14 || n.clientWidth < 64 && n.clientHeight < 64) continue;
1968
2014
  } else {
1969
- const g = o.height !== "" || o.maxHeight !== "", f = ((e = n.textContent) == null ? void 0 : e.trim().length) ?? 0;
2015
+ const g = r.height !== "" || r.maxHeight !== "", f = ((e = n.textContent) == null ? void 0 : e.trim().length) ?? 0;
1970
2016
  if (!g || f <= 50) continue;
1971
2017
  }
1972
2018
  const d = n.getAttribute("tabindex");
@@ -1982,7 +2028,7 @@ const Wt = {
1982
2028
  }
1983
2029
  return a;
1984
2030
  }
1985
- }, Pt = {
2031
+ }, Gt = {
1986
2032
  id: "accesskeys",
1987
2033
  wcag: [],
1988
2034
  level: "A",
@@ -1995,24 +2041,24 @@ const Wt = {
1995
2041
  const a = [], e = /* @__PURE__ */ new Map();
1996
2042
  for (const i of t.querySelectorAll("[accesskey]")) {
1997
2043
  if (b(i)) continue;
1998
- const r = (n = i.getAttribute("accesskey")) == null ? void 0 : n.trim().toLowerCase();
1999
- if (!r) continue;
2000
- const o = e.get(r) || [];
2001
- o.push(i), e.set(r, o);
2044
+ const o = (n = i.getAttribute("accesskey")) == null ? void 0 : n.trim().toLowerCase();
2045
+ if (!o) continue;
2046
+ const r = e.get(o) || [];
2047
+ r.push(i), e.set(o, r);
2002
2048
  }
2003
- for (const [i, r] of e)
2004
- if (r.length > 1)
2005
- for (const o of r.slice(1))
2049
+ for (const [i, o] of e)
2050
+ if (o.length > 1)
2051
+ for (const r of o.slice(1))
2006
2052
  a.push({
2007
2053
  ruleId: "accesskeys",
2008
- selector: m(o),
2009
- html: u(o),
2054
+ selector: m(r),
2055
+ html: u(r),
2010
2056
  impact: "serious",
2011
2057
  message: `Duplicate accesskey "${i}". Each accesskey must be unique.`
2012
2058
  });
2013
2059
  return a;
2014
2060
  }
2015
- }, Ot = {
2061
+ }, Yt = {
2016
2062
  id: "heading-order",
2017
2063
  wcag: [],
2018
2064
  level: "A",
@@ -2023,21 +2069,21 @@ const Wt = {
2023
2069
  run(t) {
2024
2070
  const a = [], e = t.querySelectorAll("h1, h2, h3, h4, h5, h6, [role='heading']");
2025
2071
  let n = 0, i = null;
2026
- for (const r of e) {
2027
- if (b(r)) continue;
2028
- let o;
2029
- r.hasAttribute("aria-level") ? o = parseInt(r.getAttribute("aria-level"), 10) : o = parseInt(r.tagName[1], 10), n > 0 && o > n + 1 && a.push({
2072
+ for (const o of e) {
2073
+ if (b(o)) continue;
2074
+ let r;
2075
+ o.hasAttribute("aria-level") ? r = parseInt(o.getAttribute("aria-level"), 10) : r = parseInt(o.tagName[1], 10), n > 0 && r > n + 1 && a.push({
2030
2076
  ruleId: "heading-order",
2031
- selector: m(r),
2032
- html: u(r),
2077
+ selector: m(o),
2078
+ html: u(o),
2033
2079
  impact: "moderate",
2034
- message: `Heading level ${o} skipped from level ${n}.`,
2080
+ message: `Heading level ${r} skipped from level ${n}.`,
2035
2081
  context: i ? `Previous heading: ${u(i)}` : void 0
2036
- }), n = o, i = r;
2082
+ }), n = r, i = o;
2037
2083
  }
2038
2084
  return a;
2039
2085
  }
2040
- }, H = 'article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]', oe = 'main, [role="main"], header, [role="banner"], footer, [role="contentinfo"], nav, [role="navigation"], aside, [role="complementary"], section[aria-label], section[aria-labelledby], [role="region"][aria-label], [role="region"][aria-labelledby], form[aria-label], form[aria-labelledby], [role="form"][aria-label], [role="form"][aria-labelledby], [role="search"]', Bt = {
2086
+ }, H = 'article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]', le = 'main, [role="main"], header, [role="banner"], footer, [role="contentinfo"], nav, [role="navigation"], aside, [role="complementary"], section[aria-label], section[aria-labelledby], [role="region"][aria-label], [role="region"][aria-labelledby], form[aria-label], form[aria-labelledby], [role="form"][aria-label], [role="form"][aria-labelledby], [role="search"]', Xt = {
2041
2087
  id: "landmark-one-main",
2042
2088
  wcag: [],
2043
2089
  level: "A",
@@ -2061,7 +2107,7 @@ const Wt = {
2061
2107
  message: "Page has multiple main landmarks."
2062
2108
  })) : [];
2063
2109
  }
2064
- }, Ut = {
2110
+ }, Kt = {
2065
2111
  id: "landmark-no-duplicate-banner",
2066
2112
  wcag: [],
2067
2113
  level: "A",
@@ -2081,7 +2127,7 @@ const Wt = {
2081
2127
  })
2082
2128
  ), a;
2083
2129
  }
2084
- }, _t = {
2130
+ }, Jt = {
2085
2131
  id: "landmark-no-duplicate-contentinfo",
2086
2132
  wcag: [],
2087
2133
  level: "A",
@@ -2101,7 +2147,7 @@ const Wt = {
2101
2147
  })
2102
2148
  ), a;
2103
2149
  }
2104
- }, Vt = {
2150
+ }, Qt = {
2105
2151
  id: "landmark-no-duplicate-main",
2106
2152
  wcag: [],
2107
2153
  level: "A",
@@ -2121,7 +2167,7 @@ const Wt = {
2121
2167
  })
2122
2168
  ), a;
2123
2169
  }
2124
- }, Gt = {
2170
+ }, Zt = {
2125
2171
  id: "landmark-banner-is-top-level",
2126
2172
  wcag: [],
2127
2173
  level: "A",
@@ -2141,7 +2187,7 @@ const Wt = {
2141
2187
  });
2142
2188
  return a;
2143
2189
  }
2144
- }, Yt = {
2190
+ }, ea = {
2145
2191
  id: "landmark-contentinfo-is-top-level",
2146
2192
  wcag: [],
2147
2193
  level: "A",
@@ -2161,7 +2207,7 @@ const Wt = {
2161
2207
  });
2162
2208
  return a;
2163
2209
  }
2164
- }, Xt = {
2210
+ }, ta = {
2165
2211
  id: "landmark-main-is-top-level",
2166
2212
  wcag: [],
2167
2213
  level: "A",
@@ -2183,7 +2229,7 @@ const Wt = {
2183
2229
  }
2184
2230
  return a;
2185
2231
  }
2186
- }, Kt = {
2232
+ }, aa = {
2187
2233
  id: "landmark-complementary-is-top-level",
2188
2234
  wcag: [],
2189
2235
  level: "A",
@@ -2205,7 +2251,7 @@ const Wt = {
2205
2251
  }
2206
2252
  return a;
2207
2253
  }
2208
- }, Jt = {
2254
+ }, na = {
2209
2255
  id: "landmark-unique",
2210
2256
  wcag: [],
2211
2257
  level: "A",
@@ -2221,14 +2267,14 @@ const Wt = {
2221
2267
  { selector: 'form[aria-label], form[aria-labelledby], [role="form"], [role="search"]', type: "form" }
2222
2268
  ];
2223
2269
  for (const { selector: n, type: i } of e) {
2224
- const r = Array.from(t.querySelectorAll(n)).filter((s) => !b(s));
2225
- if (r.length <= 1) continue;
2226
- const o = /* @__PURE__ */ new Map();
2227
- for (const s of r) {
2228
- const l = v(s).toLowerCase() || "", p = o.get(l) || [];
2229
- p.push(s), o.set(l, p);
2270
+ const o = Array.from(t.querySelectorAll(n)).filter((s) => !b(s));
2271
+ if (o.length <= 1) continue;
2272
+ const r = /* @__PURE__ */ new Map();
2273
+ for (const s of o) {
2274
+ const l = v(s).toLowerCase() || "", p = r.get(l) || [];
2275
+ p.push(s), r.set(l, p);
2230
2276
  }
2231
- for (const [s, l] of o)
2277
+ for (const [s, l] of r)
2232
2278
  if (l.length > 1)
2233
2279
  for (const p of l.slice(1))
2234
2280
  a.push({
@@ -2241,7 +2287,7 @@ const Wt = {
2241
2287
  }
2242
2288
  return a;
2243
2289
  }
2244
- }, Qt = {
2290
+ }, ia = {
2245
2291
  id: "region",
2246
2292
  wcag: [],
2247
2293
  level: "A",
@@ -2255,8 +2301,8 @@ const Wt = {
2255
2301
  if (!e) return [];
2256
2302
  for (const i of e.children) {
2257
2303
  if (b(i) || i instanceof HTMLScriptElement || i instanceof HTMLStyleElement || i.tagName === "NOSCRIPT" || i instanceof HTMLElement && i.hidden || i.matches('a[href^="#"]')) continue;
2258
- const r = i.matches(oe), o = (n = i.textContent) == null ? void 0 : n.trim();
2259
- !r && o && (i.querySelector(oe) || a.push({
2304
+ const o = i.matches(le), r = (n = i.textContent) == null ? void 0 : n.trim();
2305
+ !o && r && (i.querySelector(le) || a.push({
2260
2306
  ruleId: "region",
2261
2307
  selector: m(i),
2262
2308
  html: u(i),
@@ -2266,7 +2312,7 @@ const Wt = {
2266
2312
  }
2267
2313
  return a;
2268
2314
  }
2269
- }, Zt = {
2315
+ }, oa = {
2270
2316
  id: "list",
2271
2317
  selector: "ul, ol",
2272
2318
  check: { type: "child-invalid", allowedChildren: ["li", "script", "template"], allowedChildRoles: ["listitem"] },
@@ -2277,7 +2323,7 @@ const Wt = {
2277
2323
  level: "A",
2278
2324
  guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly.",
2279
2325
  prompt: "Explain how to restructure this element within the list properly."
2280
- }, ea = I(Zt), ta = {
2326
+ }, ra = E(oa), sa = {
2281
2327
  id: "dlitem",
2282
2328
  wcag: ["1.3.1"],
2283
2329
  level: "A",
@@ -2296,7 +2342,7 @@ const Wt = {
2296
2342
  });
2297
2343
  return a;
2298
2344
  }
2299
- }, aa = {
2345
+ }, la = {
2300
2346
  id: "definition-list",
2301
2347
  selector: "dl",
2302
2348
  check: { type: "child-invalid", allowedChildren: ["dt", "dd", "div", "script", "template"] },
@@ -2307,7 +2353,7 @@ const Wt = {
2307
2353
  level: "A",
2308
2354
  guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or restructure using proper definition list markup.",
2309
2355
  prompt: "Explain whether to move this element outside the <dl> or convert it to dt/dd."
2310
- }, na = I(aa), ia = {
2356
+ }, ca = E(la), da = {
2311
2357
  id: "listitem",
2312
2358
  wcag: ["1.3.1"],
2313
2359
  level: "A",
@@ -2321,8 +2367,8 @@ const Wt = {
2321
2367
  if (b(n)) continue;
2322
2368
  const i = n.parentElement;
2323
2369
  if (!i) continue;
2324
- const r = i.tagName.toLowerCase();
2325
- r === "ul" || r === "ol" || r === "menu" || ((e = i.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase()) === "list" || a.push({
2370
+ const o = i.tagName.toLowerCase();
2371
+ o === "ul" || o === "ol" || o === "menu" || ((e = i.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase()) === "list" || a.push({
2326
2372
  ruleId: "listitem",
2327
2373
  selector: m(n),
2328
2374
  html: u(n),
@@ -2332,7 +2378,7 @@ const Wt = {
2332
2378
  }
2333
2379
  return a;
2334
2380
  }
2335
- }, ra = {
2381
+ }, ua = {
2336
2382
  id: "document-title",
2337
2383
  wcag: ["2.4.2"],
2338
2384
  level: "A",
@@ -2343,13 +2389,13 @@ const Wt = {
2343
2389
  var e, n, i;
2344
2390
  const a = t.querySelector("title");
2345
2391
  if (!a || !((e = a.textContent) != null && e.trim())) {
2346
- let r;
2347
- const o = t.querySelector("h1");
2348
- if ((n = o == null ? void 0 : o.textContent) != null && n.trim())
2349
- r = `h1: "${o.textContent.trim().slice(0, 100)}"`;
2392
+ let o;
2393
+ const r = t.querySelector("h1");
2394
+ if ((n = r == null ? void 0 : r.textContent) != null && n.trim())
2395
+ o = `h1: "${r.textContent.trim().slice(0, 100)}"`;
2350
2396
  else if (t.body) {
2351
2397
  const s = ((i = t.body.textContent) == null ? void 0 : i.trim().replace(/\s+/g, " ")) || "";
2352
- s && (r = `Page text: "${s.slice(0, 150)}"`);
2398
+ s && (o = `Page text: "${s.slice(0, 150)}"`);
2353
2399
  }
2354
2400
  return [{
2355
2401
  ruleId: "document-title",
@@ -2357,12 +2403,12 @@ const Wt = {
2357
2403
  html: "<html>",
2358
2404
  impact: "serious",
2359
2405
  message: a ? "Document <title> element is empty." : "Document is missing a <title> element.",
2360
- context: r
2406
+ context: o
2361
2407
  }];
2362
2408
  }
2363
2409
  return [];
2364
2410
  }
2365
- }, oa = {
2411
+ }, ma = {
2366
2412
  id: "bypass",
2367
2413
  wcag: [],
2368
2414
  level: "A",
@@ -2376,10 +2422,10 @@ const Wt = {
2376
2422
  )) return [];
2377
2423
  const e = t.querySelector('a[href^="#"]');
2378
2424
  if (e) {
2379
- const r = e.getAttribute("href");
2380
- if (r && r.length > 1) {
2381
- const o = r.slice(1);
2382
- if (t.getElementById(o)) return [];
2425
+ const o = e.getAttribute("href");
2426
+ if (o && o.length > 1) {
2427
+ const r = o.slice(1);
2428
+ if (t.getElementById(r)) return [];
2383
2429
  }
2384
2430
  }
2385
2431
  if (t.querySelector("h1, h2, h3, [role='heading']")) return [];
@@ -2393,7 +2439,7 @@ const Wt = {
2393
2439
  context: `Missing: ${i.join(", ")}`
2394
2440
  }];
2395
2441
  }
2396
- }, sa = {
2442
+ }, pa = {
2397
2443
  id: "page-has-heading-one",
2398
2444
  wcag: [],
2399
2445
  level: "A",
@@ -2402,17 +2448,17 @@ const Wt = {
2402
2448
  guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title.",
2403
2449
  prompt: "The page has no <h1> heading. Suggest appropriate h1 text based on the page title or content sample in context. The h1 should describe the page's main topic and typically be placed at the start of the main content area.",
2404
2450
  run(t) {
2405
- var o, s, l;
2451
+ var r, s, l;
2406
2452
  const a = t.querySelector("h1");
2407
2453
  if (a && v(a)) return [];
2408
2454
  const e = t.querySelectorAll('[role="heading"][aria-level="1"]');
2409
2455
  for (const p of e)
2410
2456
  if (v(p)) return [];
2411
- const n = [], i = (s = (o = t.querySelector("title")) == null ? void 0 : o.textContent) == null ? void 0 : s.trim();
2457
+ const n = [], i = (s = (r = t.querySelector("title")) == null ? void 0 : r.textContent) == null ? void 0 : s.trim();
2412
2458
  i && n.push(`Page title: "${i}"`);
2413
- const r = t.querySelector("main");
2414
- if (r) {
2415
- const p = ((l = r.textContent) == null ? void 0 : l.trim().replace(/\s+/g, " ")) || "";
2459
+ const o = t.querySelector("main");
2460
+ if (o) {
2461
+ const p = ((l = o.textContent) == null ? void 0 : l.trim().replace(/\s+/g, " ")) || "";
2416
2462
  p && n.push(`Main content: "${p.slice(0, 100)}"`);
2417
2463
  }
2418
2464
  return [{
@@ -2425,13 +2471,13 @@ const Wt = {
2425
2471
  }];
2426
2472
  }
2427
2473
  };
2428
- function we(t) {
2474
+ function xe(t) {
2429
2475
  if (!(t instanceof HTMLElement)) return !1;
2430
2476
  if (t.style.display === "none" || t.style.visibility === "hidden") return !0;
2431
2477
  const a = t.getAttribute("width"), e = t.getAttribute("height");
2432
2478
  return (a === "0" || a === "1") && (e === "0" || e === "1");
2433
2479
  }
2434
- const la = {
2480
+ const ha = {
2435
2481
  id: "frame-title",
2436
2482
  wcag: ["4.1.2"],
2437
2483
  level: "A",
@@ -2441,7 +2487,7 @@ const la = {
2441
2487
  run(t) {
2442
2488
  const a = [];
2443
2489
  for (const e of t.querySelectorAll("iframe, frame")) {
2444
- if (b(e) || we(e)) continue;
2490
+ if (b(e) || xe(e)) continue;
2445
2491
  if (!v(e)) {
2446
2492
  const i = e.getAttribute("src");
2447
2493
  a.push({
@@ -2456,7 +2502,7 @@ const la = {
2456
2502
  }
2457
2503
  return a;
2458
2504
  }
2459
- }, ca = {
2505
+ }, ba = {
2460
2506
  id: "frame-title-unique",
2461
2507
  wcag: ["4.1.2"],
2462
2508
  level: "A",
@@ -2467,27 +2513,27 @@ const la = {
2467
2513
  run(t) {
2468
2514
  var i;
2469
2515
  const a = [], e = Array.from(t.querySelectorAll("iframe[title], frame[title]")), n = /* @__PURE__ */ new Map();
2470
- for (const r of e) {
2471
- if (b(r) || we(r)) continue;
2472
- const o = (i = r.getAttribute("title")) == null ? void 0 : i.trim().toLowerCase();
2473
- if (o) {
2474
- const s = n.get(o) || [];
2475
- s.push(r), n.set(o, s);
2516
+ for (const o of e) {
2517
+ if (b(o) || xe(o)) continue;
2518
+ const r = (i = o.getAttribute("title")) == null ? void 0 : i.trim().toLowerCase();
2519
+ if (r) {
2520
+ const s = n.get(r) || [];
2521
+ s.push(o), n.set(r, s);
2476
2522
  }
2477
2523
  }
2478
- for (const [, r] of n)
2479
- if (r.length > 1)
2480
- for (const o of r.slice(1))
2524
+ for (const [, o] of n)
2525
+ if (o.length > 1)
2526
+ for (const r of o.slice(1))
2481
2527
  a.push({
2482
2528
  ruleId: "frame-title-unique",
2483
- selector: m(o),
2484
- html: u(o),
2529
+ selector: m(r),
2530
+ html: u(r),
2485
2531
  impact: "moderate",
2486
2532
  message: "Frame title is not unique. Use a distinct title for each frame."
2487
2533
  });
2488
2534
  return a;
2489
2535
  }
2490
- }, da = {
2536
+ }, ga = {
2491
2537
  id: "empty-heading",
2492
2538
  wcag: [],
2493
2539
  level: "A",
@@ -2500,11 +2546,11 @@ const la = {
2500
2546
  const a = [], e = t.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');
2501
2547
  for (const i of e)
2502
2548
  if (!b(i) && !v(i)) {
2503
- let r;
2504
- const o = i.nextElementSibling;
2505
- if (o) {
2506
- const s = ((n = o.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
2507
- s && (r = s.slice(0, 100));
2549
+ let o;
2550
+ const r = i.nextElementSibling;
2551
+ if (r) {
2552
+ const s = ((n = r.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
2553
+ s && (o = s.slice(0, 100));
2508
2554
  }
2509
2555
  a.push({
2510
2556
  ruleId: "empty-heading",
@@ -2512,12 +2558,12 @@ const la = {
2512
2558
  html: u(i),
2513
2559
  impact: "minor",
2514
2560
  message: "Heading is empty. Add text content or remove the heading element.",
2515
- context: r ? `Following content: "${r}"` : void 0
2561
+ context: o ? `Following content: "${o}"` : void 0
2516
2562
  });
2517
2563
  }
2518
2564
  return a;
2519
2565
  }
2520
- }, ua = {
2566
+ }, fa = {
2521
2567
  id: "meta-viewport",
2522
2568
  wcag: ["1.4.4"],
2523
2569
  level: "AA",
@@ -2527,9 +2573,9 @@ const la = {
2527
2573
  run(t) {
2528
2574
  const a = [], e = t.querySelector('meta[name="viewport"]');
2529
2575
  if (!e) return [];
2530
- const n = e.getAttribute("content") || "", i = n.toLowerCase(), r = i.match(/user-scalable\s*=\s*([^\s,;]+)/i);
2531
- if (r) {
2532
- const s = r[1], l = parseFloat(s);
2576
+ const n = e.getAttribute("content") || "", i = n.toLowerCase(), o = i.match(/user-scalable\s*=\s*([^\s,;]+)/i);
2577
+ if (o) {
2578
+ const s = o[1], l = parseFloat(s);
2533
2579
  (s === "no" || !isNaN(l) && l > -1 && l < 1) && a.push({
2534
2580
  ruleId: "meta-viewport",
2535
2581
  selector: m(e),
@@ -2539,9 +2585,9 @@ const la = {
2539
2585
  context: `content: "${n}"`
2540
2586
  });
2541
2587
  }
2542
- const o = i.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);
2543
- if (o) {
2544
- const s = o[1], l = s.toLowerCase() === "yes" ? 1 : parseFloat(s);
2588
+ const r = i.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);
2589
+ if (r) {
2590
+ const s = r[1], l = s.toLowerCase() === "yes" ? 1 : parseFloat(s);
2545
2591
  l < 2 && a.push({
2546
2592
  ruleId: "meta-viewport",
2547
2593
  selector: m(e),
@@ -2553,7 +2599,7 @@ const la = {
2553
2599
  }
2554
2600
  return a;
2555
2601
  }
2556
- }, ma = {
2602
+ }, va = {
2557
2603
  id: "meta-refresh",
2558
2604
  wcag: ["2.2.1", "2.2.4", "3.2.5"],
2559
2605
  level: "A",
@@ -2584,7 +2630,7 @@ const la = {
2584
2630
  }
2585
2631
  return [];
2586
2632
  }
2587
- }, pa = {
2633
+ }, ya = {
2588
2634
  id: "blink",
2589
2635
  selector: "blink",
2590
2636
  check: { type: "selector-exists" },
@@ -2595,7 +2641,7 @@ const la = {
2595
2641
  level: "A",
2596
2642
  guidance: "Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.",
2597
2643
  prompt: "Suggest static alternatives to the blinking effect."
2598
- }, ha = I(pa), ba = {
2644
+ }, wa = E(ya), Aa = {
2599
2645
  id: "marquee",
2600
2646
  selector: "marquee",
2601
2647
  check: { type: "selector-exists" },
@@ -2606,7 +2652,7 @@ const la = {
2606
2652
  level: "A",
2607
2653
  guidance: "Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.",
2608
2654
  prompt: "Suggest static alternatives or accessible carousel patterns."
2609
- }, ga = I(ba), fa = {
2655
+ }, xa = E(Aa), ka = {
2610
2656
  id: "p-as-heading",
2611
2657
  wcag: [],
2612
2658
  level: "A",
@@ -2619,8 +2665,8 @@ const la = {
2619
2665
  const a = [];
2620
2666
  for (const i of t.querySelectorAll("p")) {
2621
2667
  if (b(i)) continue;
2622
- const r = i.getAttribute("style") || "", o = /font-weight\s*:\s*(bold|[6-9]00)/i.test(r), s = /font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(r), l = ((e = i.className) == null ? void 0 : e.toLowerCase()) || "", p = /\bh[1-6]\b|\bheading\b/.test(l), c = ((n = i.textContent) == null ? void 0 : n.trim()) || "", d = c.length > 0 && c.length < 50, h = !c.match(/[.!?,;:]$/);
2623
- if ((o && s || o && p) && d && h) {
2668
+ const o = i.getAttribute("style") || "", r = /font-weight\s*:\s*(bold|[6-9]00)/i.test(o), s = /font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(o), l = ((e = i.className) == null ? void 0 : e.toLowerCase()) || "", p = /\bh[1-6]\b|\bheading\b/.test(l), c = ((n = i.textContent) == null ? void 0 : n.trim()) || "", d = c.length > 0 && c.length < 50, h = !c.match(/[.!?,;:]$/);
2669
+ if ((r && s || r && p) && d && h) {
2624
2670
  const y = i.nextElementSibling;
2625
2671
  y && (y.tagName === "P" || y.tagName === "DIV" || y.tagName === "UL") && a.push({
2626
2672
  ruleId: "p-as-heading",
@@ -2633,7 +2679,7 @@ const la = {
2633
2679
  }
2634
2680
  return a;
2635
2681
  }
2636
- }, va = {
2682
+ }, Sa = {
2637
2683
  id: "aria-roles",
2638
2684
  wcag: ["4.1.2"],
2639
2685
  level: "A",
@@ -2643,18 +2689,18 @@ const la = {
2643
2689
  run(t) {
2644
2690
  const a = [];
2645
2691
  for (const e of t.querySelectorAll("[role]")) {
2646
- const r = e.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "").split(/\s+/).filter(Boolean);
2647
- !r.some((s) => Ce(s)) && r.length > 0 && a.push({
2692
+ const o = e.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "").split(/\s+/).filter(Boolean);
2693
+ !o.some((s) => Ne(s)) && o.length > 0 && a.push({
2648
2694
  ruleId: "aria-roles",
2649
2695
  selector: m(e),
2650
2696
  html: u(e),
2651
2697
  impact: "critical",
2652
- message: `Invalid ARIA role "${r[0]}".`
2698
+ message: `Invalid ARIA role "${o[0]}".`
2653
2699
  });
2654
2700
  }
2655
2701
  return a;
2656
2702
  }
2657
- }, ya = {
2703
+ }, Ia = {
2658
2704
  id: "aria-valid-attr",
2659
2705
  wcag: ["4.1.2"],
2660
2706
  level: "A",
@@ -2662,9 +2708,9 @@ const la = {
2662
2708
  guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).",
2663
2709
  prompt: "Identify the misspelled attribute and provide the correct spelling.",
2664
2710
  run(t) {
2665
- return X(t).validAttr;
2711
+ return J(t).validAttr;
2666
2712
  }
2667
- }, wa = {
2713
+ }, Ea = {
2668
2714
  id: "aria-valid-attr-value",
2669
2715
  wcag: ["4.1.2"],
2670
2716
  level: "A",
@@ -2672,9 +2718,9 @@ const la = {
2672
2718
  guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.",
2673
2719
  prompt: "Show the invalid value and list the valid values for this specific attribute.",
2674
2720
  run(t) {
2675
- return X(t).validAttrValue;
2721
+ return J(t).validAttrValue;
2676
2722
  }
2677
- }, Aa = {
2723
+ }, qa = {
2678
2724
  checkbox: ["aria-checked"],
2679
2725
  combobox: ["aria-expanded"],
2680
2726
  heading: ["aria-level"],
@@ -2688,7 +2734,7 @@ const la = {
2688
2734
  slider: ["aria-valuenow"],
2689
2735
  spinbutton: ["aria-valuenow"],
2690
2736
  switch: ["aria-checked"]
2691
- }, xa = {
2737
+ }, La = {
2692
2738
  id: "aria-required-attr",
2693
2739
  wcag: ["4.1.2"],
2694
2740
  level: "A",
@@ -2699,21 +2745,21 @@ const la = {
2699
2745
  const a = [];
2700
2746
  for (const e of t.querySelectorAll("[role]")) {
2701
2747
  if (b(e) || e instanceof HTMLElement && e.style.display === "none") continue;
2702
- const n = e.getAttribute("role").trim().toLowerCase(), i = Aa[n];
2748
+ const n = e.getAttribute("role").trim().toLowerCase(), i = qa[n];
2703
2749
  if (i && !(n === "checkbox" && e instanceof HTMLInputElement && e.type === "checkbox") && !(n === "radio" && e instanceof HTMLInputElement && e.type === "radio") && !(n === "option" && e instanceof HTMLOptionElement) && !(n === "heading" && /^h[1-6]$/i.test(e.tagName))) {
2704
2750
  if (n === "separator") {
2705
- const r = e.getAttribute("tabindex");
2706
- if (!r || r === "-1") continue;
2751
+ const o = e.getAttribute("tabindex");
2752
+ if (!o || o === "-1") continue;
2707
2753
  }
2708
2754
  if (!(e.tagName.toLowerCase() === "hr" && !e.hasAttribute("role"))) {
2709
- for (const r of i)
2710
- if (!e.hasAttribute(r)) {
2755
+ for (const o of i)
2756
+ if (!e.hasAttribute(o)) {
2711
2757
  a.push({
2712
2758
  ruleId: "aria-required-attr",
2713
2759
  selector: m(e),
2714
2760
  html: u(e),
2715
2761
  impact: "critical",
2716
- message: `Role "${n}" requires attribute "${r}".`
2762
+ message: `Role "${n}" requires attribute "${o}".`
2717
2763
  });
2718
2764
  break;
2719
2765
  }
@@ -2723,13 +2769,13 @@ const la = {
2723
2769
  return a;
2724
2770
  }
2725
2771
  };
2726
- function ka(t) {
2727
- var r, o, s;
2772
+ function Ta(t) {
2773
+ var o, r, s;
2728
2774
  const a = [], e = t.className;
2729
2775
  e && typeof e == "string" && e.trim() && a.push(`Classes: ${e.trim().slice(0, 100)}`);
2730
2776
  const n = t.closest("form");
2731
2777
  if (n) {
2732
- const l = n.getAttribute("aria-label") || ((o = (r = n.querySelector("legend")) == null ? void 0 : r.textContent) == null ? void 0 : o.trim());
2778
+ const l = n.getAttribute("aria-label") || ((r = (o = n.querySelector("legend")) == null ? void 0 : o.textContent) == null ? void 0 : r.trim());
2733
2779
  l && a.push(`Form: ${l.slice(0, 60)}`);
2734
2780
  }
2735
2781
  const i = t.parentElement;
@@ -2740,7 +2786,7 @@ function ka(t) {
2740
2786
  return a.length > 0 ? a.join(`
2741
2787
  `) : void 0;
2742
2788
  }
2743
- const Sa = {
2789
+ const Ra = {
2744
2790
  id: "button-name",
2745
2791
  wcag: ["4.1.2"],
2746
2792
  level: "A",
@@ -2759,12 +2805,12 @@ const Sa = {
2759
2805
  html: u(e),
2760
2806
  impact: "critical",
2761
2807
  message: "Button has no discernible text.",
2762
- context: ka(e)
2808
+ context: Ta(e)
2763
2809
  });
2764
2810
  }
2765
2811
  return a;
2766
2812
  }
2767
- }, Ia = {
2813
+ }, Ca = {
2768
2814
  alert: /* @__PURE__ */ new Set(["aria-atomic", "aria-busy", "aria-live", "aria-relevant"]),
2769
2815
  alertdialog: /* @__PURE__ */ new Set(["aria-describedby", "aria-modal"]),
2770
2816
  application: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-disabled", "aria-errormessage", "aria-expanded", "aria-haspopup", "aria-invalid"]),
@@ -2836,7 +2882,7 @@ const Sa = {
2836
2882
  tree: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-disabled", "aria-errormessage", "aria-invalid", "aria-multiselectable", "aria-orientation", "aria-required"]),
2837
2883
  treegrid: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-colcount", "aria-disabled", "aria-errormessage", "aria-invalid", "aria-multiselectable", "aria-orientation", "aria-readonly", "aria-required", "aria-rowcount"]),
2838
2884
  treeitem: /* @__PURE__ */ new Set(["aria-checked", "aria-disabled", "aria-expanded", "aria-haspopup", "aria-level", "aria-posinset", "aria-selected", "aria-setsize"])
2839
- }, qa = /* @__PURE__ */ new Set([
2885
+ }, Na = /* @__PURE__ */ new Set([
2840
2886
  "aria-atomic",
2841
2887
  "aria-busy",
2842
2888
  "aria-controls",
@@ -2860,7 +2906,7 @@ const Sa = {
2860
2906
  "aria-roledescription",
2861
2907
  "aria-braillelabel",
2862
2908
  "aria-brailleroledescription"
2863
- ]), La = {
2909
+ ]), Ma = {
2864
2910
  id: "aria-allowed-attr",
2865
2911
  wcag: ["4.1.2"],
2866
2912
  level: "A",
@@ -2871,26 +2917,26 @@ const Sa = {
2871
2917
  const a = [];
2872
2918
  for (const e of t.querySelectorAll("[role], [aria-*]")) {
2873
2919
  if (b(e)) continue;
2874
- const n = R(e);
2920
+ const n = N(e);
2875
2921
  if (!n) continue;
2876
- const i = Ia[n];
2922
+ const i = Ca[n];
2877
2923
  if (i)
2878
- for (const r of e.attributes) {
2879
- if (!r.name.startsWith("aria-") || qa.has(r.name) || i.has(r.name)) continue;
2880
- const o = i.size > 0 ? [...i].join(", ") : "none (only global ARIA attributes)";
2924
+ for (const o of e.attributes) {
2925
+ if (!o.name.startsWith("aria-") || Na.has(o.name) || i.has(o.name)) continue;
2926
+ const r = i.size > 0 ? [...i].join(", ") : "none (only global ARIA attributes)";
2881
2927
  a.push({
2882
2928
  ruleId: "aria-allowed-attr",
2883
2929
  selector: m(e),
2884
2930
  html: u(e),
2885
2931
  impact: "critical",
2886
- message: `ARIA attribute "${r.name}" is not allowed on role "${n}".`,
2887
- context: `Attribute: ${r.name}="${r.value}", role: ${n}, allowed role-specific attributes: ${o}`
2932
+ message: `ARIA attribute "${o.name}" is not allowed on role "${n}".`,
2933
+ context: `Attribute: ${o.name}="${o.value}", role: ${n}, allowed role-specific attributes: ${r}`
2888
2934
  });
2889
2935
  }
2890
2936
  }
2891
2937
  return a;
2892
2938
  }
2893
- }, Ea = /* @__PURE__ */ new Set([
2939
+ }, Da = /* @__PURE__ */ new Set([
2894
2940
  "base",
2895
2941
  "col",
2896
2942
  "colgroup",
@@ -3012,10 +3058,10 @@ const Sa = {
3012
3058
  video: /* @__PURE__ */ new Set(["application"]),
3013
3059
  wbr: /* @__PURE__ */ new Set(["none", "presentation"])
3014
3060
  };
3015
- function Ta(t) {
3061
+ function $a(t) {
3016
3062
  var e;
3017
3063
  const a = t.tagName.toLowerCase();
3018
- if (Ea.has(a))
3064
+ if (Da.has(a))
3019
3065
  return "none";
3020
3066
  if (a === "a" && t.hasAttribute("href"))
3021
3067
  return L["a[href]"];
@@ -3027,7 +3073,7 @@ function Ta(t) {
3027
3073
  }
3028
3074
  return L[a] || "any";
3029
3075
  }
3030
- const Ca = {
3076
+ const Ha = {
3031
3077
  id: "aria-allowed-role",
3032
3078
  wcag: ["4.1.2"],
3033
3079
  level: "A",
@@ -3041,16 +3087,16 @@ const Ca = {
3041
3087
  if (b(n)) continue;
3042
3088
  const i = (e = n.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase();
3043
3089
  if (!i) continue;
3044
- const r = ge(n);
3045
- if (r && i === r) continue;
3046
- const o = Ta(n);
3047
- o === "none" ? a.push({
3090
+ const o = ve(n);
3091
+ if (o && i === o) continue;
3092
+ const r = $a(n);
3093
+ r === "none" ? a.push({
3048
3094
  ruleId: "aria-allowed-role",
3049
3095
  selector: m(n),
3050
3096
  html: u(n),
3051
3097
  impact: "minor",
3052
3098
  message: `Element <${n.tagName.toLowerCase()}> should not have an explicit role.`
3053
- }) : o !== "any" && !o.has(i) && a.push({
3099
+ }) : r !== "any" && !r.has(i) && a.push({
3054
3100
  ruleId: "aria-allowed-role",
3055
3101
  selector: m(n),
3056
3102
  html: u(n),
@@ -3060,7 +3106,7 @@ const Ca = {
3060
3106
  }
3061
3107
  return a;
3062
3108
  }
3063
- }, se = {
3109
+ }, ce = {
3064
3110
  // Each array is an OR group - at least one of each inner array must be present
3065
3111
  combobox: [["listbox", "tree", "grid", "dialog", "textbox"]],
3066
3112
  // Must own/contain one of these
@@ -3077,7 +3123,7 @@ const Ca = {
3077
3123
  tablist: [["tab"]],
3078
3124
  tree: [["treeitem", "group"]],
3079
3125
  treegrid: [["row", "rowgroup"]]
3080
- }, Ra = /* @__PURE__ */ new Set([
3126
+ }, Pa = /* @__PURE__ */ new Set([
3081
3127
  "doc-bibliography",
3082
3128
  "doc-endnotes",
3083
3129
  "grid",
@@ -3089,7 +3135,7 @@ const Ca = {
3089
3135
  "tablist",
3090
3136
  "tree",
3091
3137
  "treegrid"
3092
- ]), le = {
3138
+ ]), de = {
3093
3139
  caption: ["figure", "table", "grid", "treegrid"],
3094
3140
  cell: ["row"],
3095
3141
  columnheader: ["row"],
@@ -3105,30 +3151,30 @@ const Ca = {
3105
3151
  tab: ["tablist"],
3106
3152
  treeitem: ["tree", "group"]
3107
3153
  };
3108
- function Na(t, a) {
3109
- var o;
3110
- const e = ((o = t.getAttribute("aria-owns")) == null ? void 0 : o.split(/\s+/)) || [], n = t.ownerDocument, i = /* @__PURE__ */ new Set();
3111
- let r = !1;
3154
+ function za(t, a) {
3155
+ var r;
3156
+ const e = ((r = t.getAttribute("aria-owns")) == null ? void 0 : r.split(/\s+/)) || [], n = t.ownerDocument, i = /* @__PURE__ */ new Set();
3157
+ let o = !1;
3112
3158
  for (const s of t.querySelectorAll("*")) {
3113
3159
  if (b(s)) continue;
3114
- r = !0;
3115
- const l = R(s);
3160
+ o = !0;
3161
+ const l = N(s);
3116
3162
  l && i.add(l);
3117
3163
  }
3118
3164
  for (const s of e) {
3119
3165
  const l = n.getElementById(s);
3120
3166
  if (l && !b(l)) {
3121
- r = !0;
3122
- const p = R(l);
3167
+ o = !0;
3168
+ const p = N(l);
3123
3169
  p && i.add(p);
3124
3170
  }
3125
3171
  }
3126
- if (!r) return "empty";
3172
+ if (!o) return "empty";
3127
3173
  for (const s of a)
3128
3174
  if (!s.some((l) => i.has(l))) return "fail";
3129
3175
  return "pass";
3130
3176
  }
3131
- const Ma = {
3177
+ const Fa = {
3132
3178
  id: "aria-required-children",
3133
3179
  wcag: ["1.3.1"],
3134
3180
  level: "A",
@@ -3141,15 +3187,15 @@ const Ma = {
3141
3187
  for (const n of t.querySelectorAll("[role]")) {
3142
3188
  if (b(n)) continue;
3143
3189
  const i = (e = n.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase();
3144
- if (!i || !(i in se) || n.getAttribute("aria-busy") === "true") continue;
3190
+ if (!i || !(i in ce) || n.getAttribute("aria-busy") === "true") continue;
3145
3191
  if (i === "combobox") {
3146
3192
  if (n.getAttribute("aria-expanded") !== "true") continue;
3147
3193
  const l = n.tagName.toLowerCase();
3148
3194
  if (l === "input" || l === "textarea") continue;
3149
3195
  }
3150
- const r = se[i], o = Na(n, r);
3151
- if (o === "pass" || o === "empty" && Ra.has(i)) continue;
3152
- const s = r.map((l) => l.join(" or ")).join(", ");
3196
+ const o = ce[i], r = za(n, o);
3197
+ if (r === "pass" || r === "empty" && Pa.has(i)) continue;
3198
+ const s = o.map((l) => l.join(" or ")).join(", ");
3153
3199
  a.push({
3154
3200
  ruleId: "aria-required-children",
3155
3201
  selector: m(n),
@@ -3160,7 +3206,7 @@ const Ma = {
3160
3206
  }
3161
3207
  return a;
3162
3208
  }
3163
- }, $a = {
3209
+ }, ja = {
3164
3210
  id: "aria-required-parent",
3165
3211
  wcag: ["1.3.1"],
3166
3212
  level: "A",
@@ -3173,28 +3219,28 @@ const Ma = {
3173
3219
  for (const n of t.querySelectorAll("[role]")) {
3174
3220
  if (b(n)) continue;
3175
3221
  const i = (e = n.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase();
3176
- if (!i || !(i in le)) continue;
3177
- const r = le[i];
3178
- let o = n.parentElement, s = !1;
3179
- for (; o && o !== t.documentElement; ) {
3180
- const l = R(o);
3181
- if (l && r.includes(l)) {
3222
+ if (!i || !(i in de)) continue;
3223
+ const o = de[i];
3224
+ let r = n.parentElement, s = !1;
3225
+ for (; r && r !== t.documentElement; ) {
3226
+ const l = N(r);
3227
+ if (l && o.includes(l)) {
3182
3228
  s = !0;
3183
3229
  break;
3184
3230
  }
3185
- o = o.parentElement;
3231
+ r = r.parentElement;
3186
3232
  }
3187
3233
  s || a.push({
3188
3234
  ruleId: "aria-required-parent",
3189
3235
  selector: m(n),
3190
3236
  html: u(n),
3191
3237
  impact: "critical",
3192
- message: `Role "${i}" must be contained within: ${r.join(", ")}.`
3238
+ message: `Role "${i}" must be contained within: ${o.join(", ")}.`
3193
3239
  });
3194
3240
  }
3195
3241
  return a;
3196
3242
  }
3197
- }, ce = [
3243
+ }, ue = [
3198
3244
  "a[href]",
3199
3245
  "button:not([disabled])",
3200
3246
  'input:not([disabled]):not([type="hidden"])',
@@ -3210,7 +3256,7 @@ const Ma = {
3210
3256
  "embed",
3211
3257
  "area[href]"
3212
3258
  ].join(", ");
3213
- function Ha(t) {
3259
+ function Wa(t) {
3214
3260
  let a = t;
3215
3261
  const e = t.ownerDocument, n = e.defaultView;
3216
3262
  for (; a && a !== e.body; ) {
@@ -3223,7 +3269,7 @@ function Ha(t) {
3223
3269
  }
3224
3270
  return !0;
3225
3271
  }
3226
- const Da = {
3272
+ const Ua = {
3227
3273
  id: "aria-hidden-body",
3228
3274
  selector: 'body[aria-hidden="true"]',
3229
3275
  check: { type: "selector-exists" },
@@ -3235,7 +3281,7 @@ const Da = {
3235
3281
  guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",
3236
3282
  prompt: "Instruct to remove aria-hidden='true' from the body element.",
3237
3283
  skipAriaHidden: !1
3238
- }, za = I(Da), ja = {
3284
+ }, Ba = E(Ua), Oa = {
3239
3285
  id: "aria-hidden-focus",
3240
3286
  wcag: ["4.1.2"],
3241
3287
  level: "A",
@@ -3246,15 +3292,15 @@ const Da = {
3246
3292
  const a = [];
3247
3293
  for (const e of t.querySelectorAll('[aria-hidden="true"]')) {
3248
3294
  if (e === t.body) continue;
3249
- const n = [...e.querySelectorAll(ce)];
3250
- e.matches(ce) && n.push(e);
3295
+ const n = [...e.querySelectorAll(ue)];
3296
+ e.matches(ue) && n.push(e);
3251
3297
  for (const i of n)
3252
3298
  if (i instanceof HTMLElement) {
3253
- const r = i.getAttribute("tabindex");
3254
- if (r === "-1" || i.disabled || i instanceof HTMLInputElement && i.type === "hidden" || !Ha(i)) continue;
3255
- const o = i.tagName.toLowerCase();
3299
+ const o = i.getAttribute("tabindex");
3300
+ if (o === "-1" || i.disabled || i instanceof HTMLInputElement && i.type === "hidden" || !Wa(i)) continue;
3301
+ const r = i.tagName.toLowerCase();
3256
3302
  let s;
3257
- r !== null ? s = `has tabindex="${r}"` : o === "a" && i.hasAttribute("href") ? s = "is a link with href" : o === "button" ? s = "is a <button>" : o === "input" ? s = `is an <input type="${i.type}">` : o === "select" ? s = "is a <select>" : o === "textarea" ? s = "is a <textarea>" : o === "iframe" ? s = "is an <iframe>" : s = `is a natively focusable <${o}>`;
3303
+ o !== null ? s = `has tabindex="${o}"` : r === "a" && i.hasAttribute("href") ? s = "is a link with href" : r === "button" ? s = "is a <button>" : r === "input" ? s = `is an <input type="${i.type}">` : r === "select" ? s = "is a <select>" : r === "textarea" ? s = "is a <textarea>" : r === "iframe" ? s = "is an <iframe>" : s = `is a natively focusable <${r}>`;
3258
3304
  const l = i === e ? i : i.closest('[aria-hidden="true"]');
3259
3305
  a.push({
3260
3306
  ruleId: "aria-hidden-focus",
@@ -3268,7 +3314,7 @@ const Da = {
3268
3314
  }
3269
3315
  return a;
3270
3316
  }
3271
- }, Wa = {
3317
+ }, Va = {
3272
3318
  id: "aria-command-name",
3273
3319
  wcag: ["4.1.2"],
3274
3320
  level: "A",
@@ -3281,8 +3327,8 @@ const Da = {
3281
3327
  for (const n of t.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')) {
3282
3328
  if (b(n) || q(n) || n.getRootNode() instanceof ShadowRoot || n.tagName.toLowerCase() === "button" || n.tagName.toLowerCase() === "a") continue;
3283
3329
  if (!v(n)) {
3284
- const r = n.querySelector("img[alt]");
3285
- if ((e = r == null ? void 0 : r.getAttribute("alt")) != null && e.trim()) continue;
3330
+ const o = n.querySelector("img[alt]");
3331
+ if ((e = o == null ? void 0 : o.getAttribute("alt")) != null && e.trim()) continue;
3286
3332
  a.push({
3287
3333
  ruleId: "aria-command-name",
3288
3334
  selector: m(n),
@@ -3294,7 +3340,7 @@ const Da = {
3294
3340
  }
3295
3341
  return a;
3296
3342
  }
3297
- }, Fa = {
3343
+ }, _a = {
3298
3344
  id: "aria-input-field-name",
3299
3345
  wcag: ["4.1.2"],
3300
3346
  level: "A",
@@ -3315,7 +3361,7 @@ const Da = {
3315
3361
  }
3316
3362
  return a;
3317
3363
  }
3318
- }, Pa = {
3364
+ }, Ga = {
3319
3365
  id: "aria-toggle-field-name",
3320
3366
  wcag: ["4.1.2"],
3321
3367
  level: "A",
@@ -3336,7 +3382,7 @@ const Da = {
3336
3382
  }
3337
3383
  return a;
3338
3384
  }
3339
- }, Oa = {
3385
+ }, Ya = {
3340
3386
  id: "aria-meter-name",
3341
3387
  wcag: ["4.1.2"],
3342
3388
  level: "A",
@@ -3357,7 +3403,7 @@ const Da = {
3357
3403
  }
3358
3404
  return a;
3359
3405
  }
3360
- }, Ba = {
3406
+ }, Xa = {
3361
3407
  id: "aria-progressbar-name",
3362
3408
  wcag: ["4.1.2"],
3363
3409
  level: "A",
@@ -3378,7 +3424,7 @@ const Da = {
3378
3424
  }
3379
3425
  return a;
3380
3426
  }
3381
- }, Ua = {
3427
+ }, Ka = {
3382
3428
  id: "aria-dialog-name",
3383
3429
  wcag: ["4.1.2"],
3384
3430
  level: "A",
@@ -3399,7 +3445,7 @@ const Da = {
3399
3445
  }
3400
3446
  return a;
3401
3447
  }
3402
- }, _a = {
3448
+ }, Ja = {
3403
3449
  id: "aria-tooltip-name",
3404
3450
  wcag: ["4.1.2"],
3405
3451
  level: "A",
@@ -3420,7 +3466,7 @@ const Da = {
3420
3466
  }
3421
3467
  return a;
3422
3468
  }
3423
- }, Va = {
3469
+ }, Qa = {
3424
3470
  id: "aria-treeitem-name",
3425
3471
  wcag: ["4.1.2"],
3426
3472
  level: "A",
@@ -3441,7 +3487,7 @@ const Da = {
3441
3487
  }
3442
3488
  return a;
3443
3489
  }
3444
- }, Ga = {
3490
+ }, Za = {
3445
3491
  id: "aria-prohibited-attr",
3446
3492
  wcag: ["4.1.2"],
3447
3493
  level: "A",
@@ -3449,16 +3495,16 @@ const Da = {
3449
3495
  guidance: "Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.",
3450
3496
  prompt: "Identify the prohibited attribute and recommend removing it from this element.",
3451
3497
  run(t) {
3452
- return X(t).prohibitedAttr;
3498
+ return J(t).prohibitedAttr;
3453
3499
  }
3454
- }, Ya = [
3500
+ }, en = [
3455
3501
  "a[href]",
3456
3502
  "button:not([disabled])",
3457
3503
  'input:not([disabled]):not([type="hidden"])',
3458
3504
  "select:not([disabled])",
3459
3505
  "textarea:not([disabled])",
3460
3506
  '[tabindex]:not([tabindex="-1"])'
3461
- ].join(", "), Xa = [
3507
+ ].join(", "), tn = [
3462
3508
  "aria-atomic",
3463
3509
  "aria-busy",
3464
3510
  "aria-controls",
@@ -3473,17 +3519,17 @@ const Da = {
3473
3519
  "aria-owns",
3474
3520
  "aria-relevant"
3475
3521
  ];
3476
- function de(t) {
3522
+ function me(t) {
3477
3523
  const a = [];
3478
- t.matches(Ya) && a.push("element is focusable");
3479
- for (const e of Xa)
3524
+ t.matches(en) && a.push("element is focusable");
3525
+ for (const e of tn)
3480
3526
  if (t.hasAttribute(e)) {
3481
3527
  a.push(`has ${e}`);
3482
3528
  break;
3483
3529
  }
3484
3530
  return (t.hasAttribute("aria-label") || t.hasAttribute("aria-labelledby")) && a.push("has accessible name"), a;
3485
3531
  }
3486
- const Ka = {
3532
+ const an = {
3487
3533
  id: "presentation-role-conflict",
3488
3534
  wcag: ["4.1.2"],
3489
3535
  level: "A",
@@ -3494,7 +3540,7 @@ const Ka = {
3494
3540
  const a = [];
3495
3541
  for (const e of t.querySelectorAll('[role="presentation"], [role="none"]')) {
3496
3542
  if (b(e)) continue;
3497
- const n = de(e);
3543
+ const n = me(e);
3498
3544
  n.length > 0 && a.push({
3499
3545
  ruleId: "presentation-role-conflict",
3500
3546
  selector: m(e),
@@ -3505,7 +3551,7 @@ const Ka = {
3505
3551
  }
3506
3552
  for (const e of t.querySelectorAll('img[alt=""]')) {
3507
3553
  if (b(e) || e.hasAttribute("role")) continue;
3508
- const n = de(e);
3554
+ const n = me(e);
3509
3555
  n.length > 0 && a.push({
3510
3556
  ruleId: "presentation-role-conflict",
3511
3557
  selector: m(e),
@@ -3516,7 +3562,7 @@ const Ka = {
3516
3562
  }
3517
3563
  return a;
3518
3564
  }
3519
- }, Ja = {
3565
+ }, nn = {
3520
3566
  id: "summary-name",
3521
3567
  wcag: ["4.1.2"],
3522
3568
  level: "A",
@@ -3538,24 +3584,24 @@ const Ka = {
3538
3584
  return a;
3539
3585
  }
3540
3586
  };
3541
- function Qa(t) {
3542
- var i, r;
3587
+ function on(t) {
3588
+ var i, o;
3543
3589
  const a = [], e = t.getAttribute("href");
3544
3590
  e && a.push(`href: ${e}`);
3545
3591
  const n = t.parentElement;
3546
3592
  if (n) {
3547
- const o = n.closest("h1, h2, h3, h4, h5, h6");
3548
- if ((i = o == null ? void 0 : o.textContent) != null && i.trim())
3549
- a.push(`Nearby heading: ${o.textContent.trim().slice(0, 80)}`);
3593
+ const r = n.closest("h1, h2, h3, h4, h5, h6");
3594
+ if ((i = r == null ? void 0 : r.textContent) != null && i.trim())
3595
+ a.push(`Nearby heading: ${r.textContent.trim().slice(0, 80)}`);
3550
3596
  else {
3551
- const s = (r = n.textContent) == null ? void 0 : r.trim().slice(0, 100);
3597
+ const s = (o = n.textContent) == null ? void 0 : o.trim().slice(0, 100);
3552
3598
  s && a.push(`Parent text: ${s}`);
3553
3599
  }
3554
3600
  }
3555
3601
  return a.length > 0 ? a.join(`
3556
3602
  `) : void 0;
3557
3603
  }
3558
- const Za = {
3604
+ const rn = {
3559
3605
  id: "link-name",
3560
3606
  wcag: ["2.4.4", "4.1.2"],
3561
3607
  level: "A",
@@ -3572,12 +3618,12 @@ const Za = {
3572
3618
  html: u(e),
3573
3619
  impact: "serious",
3574
3620
  message: "Link has no discernible text.",
3575
- context: Qa(e)
3621
+ context: on(e)
3576
3622
  });
3577
3623
  }
3578
3624
  return a;
3579
3625
  }
3580
- }, en = {
3626
+ }, sn = {
3581
3627
  id: "skip-link",
3582
3628
  wcag: ["2.4.1"],
3583
3629
  level: "A",
@@ -3590,8 +3636,8 @@ const Za = {
3590
3636
  for (const n of e) {
3591
3637
  const i = n.getAttribute("href");
3592
3638
  if (!i || i === "#") continue;
3593
- const r = A(n).toLowerCase();
3594
- if (!(r.includes("skip") || r.includes("jump") || r.includes("main content") || r.includes("navigation"))) continue;
3639
+ const o = A(n).toLowerCase();
3640
+ if (!(o.includes("skip") || o.includes("jump") || o.includes("main content") || o.includes("navigation"))) continue;
3595
3641
  const s = i.slice(1);
3596
3642
  t.getElementById(s) || a.push({
3597
3643
  ruleId: "skip-link",
@@ -3603,7 +3649,7 @@ const Za = {
3603
3649
  }
3604
3650
  return a;
3605
3651
  }
3606
- }, tn = /* @__PURE__ */ new Set([
3652
+ }, ln = /* @__PURE__ */ new Set([
3607
3653
  "block",
3608
3654
  "flex",
3609
3655
  "grid",
@@ -3611,23 +3657,23 @@ const Za = {
3611
3657
  "table-cell",
3612
3658
  "list-item",
3613
3659
  "flow-root"
3614
- ]), an = /* @__PURE__ */ new Set([
3660
+ ]), cn = /* @__PURE__ */ new Set([
3615
3661
  "inline",
3616
3662
  "inline-block",
3617
3663
  "inline-flex",
3618
3664
  "inline-grid"
3619
3665
  ]);
3620
- function nn(t) {
3666
+ function dn(t) {
3621
3667
  let a = t.parentElement;
3622
3668
  for (; a; ) {
3623
3669
  const e = w(a).display;
3624
- if (tn.has(e))
3625
- return rn(a) ? a : null;
3670
+ if (ln.has(e))
3671
+ return un(a) ? a : null;
3626
3672
  a = a.parentElement;
3627
3673
  }
3628
3674
  return null;
3629
3675
  }
3630
- function rn(t) {
3676
+ function un(t) {
3631
3677
  const a = t.ownerDocument.createTreeWalker(
3632
3678
  t,
3633
3679
  NodeFilter.SHOW_TEXT
@@ -3635,19 +3681,19 @@ function rn(t) {
3635
3681
  let e = "", n;
3636
3682
  for (; n = a.nextNode(); ) {
3637
3683
  if (!n.data.trim()) continue;
3638
- let i = n.parentElement, r = !1;
3684
+ let i = n.parentElement, o = !1;
3639
3685
  for (; i && i !== t; ) {
3640
3686
  if (i.tagName === "A") {
3641
- r = !0;
3687
+ o = !0;
3642
3688
  break;
3643
3689
  }
3644
3690
  i = i.parentElement;
3645
3691
  }
3646
- r || (e += n.data);
3692
+ o || (e += n.data);
3647
3693
  }
3648
3694
  return new RegExp("\\p{L}{2,}", "u").test(e);
3649
3695
  }
3650
- function on(t, a) {
3696
+ function mn(t, a) {
3651
3697
  const e = t.ownerDocument.createTreeWalker(
3652
3698
  t,
3653
3699
  NodeFilter.SHOW_TEXT
@@ -3655,25 +3701,25 @@ function on(t, a) {
3655
3701
  let n;
3656
3702
  for (; n = e.nextNode(); ) {
3657
3703
  if (!n.data.trim()) continue;
3658
- let i = n.parentElement, r = !1, o = i;
3659
- for (; o && o !== t; ) {
3660
- if (o.tagName === "A") {
3661
- r = !0;
3704
+ let i = n.parentElement, o = !1, r = i;
3705
+ for (; r && r !== t; ) {
3706
+ if (r.tagName === "A") {
3707
+ o = !0;
3662
3708
  break;
3663
3709
  }
3664
- o = o.parentElement;
3710
+ r = r.parentElement;
3665
3711
  }
3666
- if (!r && i)
3667
- return $(w(i).color);
3712
+ if (!o && i)
3713
+ return D(w(i).color);
3668
3714
  }
3669
3715
  return null;
3670
3716
  }
3671
- function sn(t, a, e) {
3717
+ function pn(t, a, e) {
3672
3718
  const n = e.textDecorationLine || e.textDecoration || "", i = a.textDecorationLine || a.textDecoration || "";
3673
3719
  if ((i.includes("underline") || i.includes("line-through")) && i !== n)
3674
3720
  return !0;
3675
- const r = parseFloat(a.borderBottomWidth) || 0, o = a.borderBottomStyle || "";
3676
- if (r > 0 && o !== "none" && o !== "hidden")
3721
+ const o = parseFloat(a.borderBottomWidth) || 0, r = a.borderBottomStyle || "";
3722
+ if (o > 0 && r !== "none" && r !== "hidden")
3677
3723
  return !0;
3678
3724
  const s = parseFloat(a.outlineWidth) || 0, l = a.outlineStyle || "";
3679
3725
  if (s > 0 && l !== "none")
@@ -3681,7 +3727,7 @@ function sn(t, a, e) {
3681
3727
  const p = a.backgroundImage || "";
3682
3728
  if (p && p !== "none" && p !== "initial")
3683
3729
  return !0;
3684
- const c = W(e.fontWeight), d = W(a.fontWeight);
3730
+ const c = j(e.fontWeight), d = j(a.fontWeight);
3685
3731
  if (Math.abs(d - c) >= 300 || a.fontStyle !== e.fontStyle)
3686
3732
  return !0;
3687
3733
  const h = parseFloat(a.fontSize) || 16, g = parseFloat(e.fontSize) || 16;
@@ -3689,18 +3735,18 @@ function sn(t, a, e) {
3689
3735
  return !0;
3690
3736
  for (const f of t.querySelectorAll("*")) {
3691
3737
  const y = w(f), x = y.textDecorationLine || y.textDecoration || "";
3692
- if ((x.includes("underline") || x.includes("line-through")) && x !== n || Math.abs(W(y.fontWeight) - c) >= 300)
3738
+ if ((x.includes("underline") || x.includes("line-through")) && x !== n || Math.abs(j(y.fontWeight) - c) >= 300)
3693
3739
  return !0;
3694
3740
  }
3695
3741
  return !1;
3696
3742
  }
3697
- function W(t) {
3743
+ function j(t) {
3698
3744
  return t === "bold" ? 700 : t === "normal" ? 400 : parseInt(t) || 400;
3699
3745
  }
3700
- function ue(t, a, e) {
3746
+ function pe(t, a, e) {
3701
3747
  return "#" + [t, a, e].map((n) => n.toString(16).padStart(2, "0")).join("");
3702
3748
  }
3703
- const ln = {
3749
+ const hn = {
3704
3750
  id: "link-in-text-block",
3705
3751
  wcag: ["1.4.1"],
3706
3752
  level: "A",
@@ -3712,16 +3758,16 @@ const ln = {
3712
3758
  for (const e of t.querySelectorAll("a[href]")) {
3713
3759
  if (b(e) || !A(e).trim() || e.closest('nav, header, footer, [role="navigation"], [role="banner"], [role="contentinfo"]')) continue;
3714
3760
  const n = w(e), i = n.display || "inline";
3715
- if (!an.has(i)) continue;
3716
- const r = nn(e);
3717
- if (!r) continue;
3718
- const o = w(r);
3719
- if (sn(e, n, o)) continue;
3720
- const s = $(n.color), l = on(r);
3761
+ if (!cn.has(i)) continue;
3762
+ const o = dn(e);
3763
+ if (!o) continue;
3764
+ const r = w(o);
3765
+ if (pn(e, n, r)) continue;
3766
+ const s = D(n.color), l = mn(o);
3721
3767
  if (!s || !l) continue;
3722
- const p = N(...s), c = N(...l), d = ve(p, c);
3768
+ const p = M(...s), c = M(...l), d = we(p, c);
3723
3769
  if (d < 1.1 || d >= 3) continue;
3724
- const h = ue(...s), g = ue(...l), f = `link color: ${h} rgb(${s.join(", ")}), surrounding text: ${g} rgb(${l.join(", ")}), ratio: ${d.toFixed(2)}:1`;
3770
+ const h = pe(...s), g = pe(...l), f = `link color: ${h} rgb(${s.join(", ")}), surrounding text: ${g} rgb(${l.join(", ")}), ratio: ${d.toFixed(2)}:1`;
3725
3771
  a.push({
3726
3772
  ruleId: "link-in-text-block",
3727
3773
  selector: m(e),
@@ -3733,7 +3779,7 @@ const ln = {
3733
3779
  }
3734
3780
  return a;
3735
3781
  }
3736
- }, cn = {
3782
+ }, bn = {
3737
3783
  id: "html-has-lang",
3738
3784
  wcag: ["3.1.1"],
3739
3785
  level: "A",
@@ -3747,14 +3793,14 @@ const ln = {
3747
3793
  if (!t.doctype && t.body) {
3748
3794
  const i = t.body.children;
3749
3795
  if (i.length > 0 && Array.from(i).every(
3750
- (r) => r.tagName.toLowerCase() === "svg" || r.tagName.toLowerCase() === "math"
3796
+ (o) => o.tagName.toLowerCase() === "svg" || o.tagName.toLowerCase() === "math"
3751
3797
  )) return [];
3752
3798
  }
3753
3799
  if (!((e = a.getAttribute("lang")) != null && e.trim())) {
3754
3800
  let i;
3755
3801
  if (t.body) {
3756
- const r = ((n = t.body.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
3757
- r && (i = r.slice(0, 200));
3802
+ const o = ((n = t.body.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
3803
+ o && (i = o.slice(0, 200));
3758
3804
  }
3759
3805
  return [{
3760
3806
  ruleId: "html-has-lang",
@@ -3767,17 +3813,17 @@ const ln = {
3767
3813
  }
3768
3814
  return [];
3769
3815
  }
3770
- }, dn = new Set(
3816
+ }, gn = new Set(
3771
3817
  "aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ch co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu".split(" ")
3772
- ), un = new Set(
3818
+ ), fn = new Set(
3773
3819
  "aar abk afr aka amh ara arg asm ava ave aym aze bak bam bel ben bih bis bod bos bre bul cat ces cha che chu chv cor cos cre cym dan deu div dzo ell eng epo est eus ewe fao fas fij fin fra fry ful gla gle glg glv grn guj hat hau hbs heb her hin hmo hrv hun hye ibo iii iku ile ina ind ipk isl ita jav jpn kal kan kas kat kau kaz khm kik kin kir kom kon kor kua kur lao lat lav lim lin lit ltz lub lug mah mal mar mkd mlg mlt mon mri msa mya nau nav nbl nde ndo nep nld nno nob nor nya oci oji ori orm oss pan pli pol por pus que roh ron run rus sag san sin slk slv sme smo sna snd som sot spa sqi srd srp ssw sun swa swe tah tam tat tel tgk tgl tha tir ton tsn tso tuk tur twi uig ukr urd uzb ven vie vol wln wol xho yid yor zha zho zul".split(" ")
3774
- ), mn = /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
3775
- function Ae(t) {
3776
- if (!mn.test(t)) return !1;
3820
+ ), vn = /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
3821
+ function ke(t) {
3822
+ if (!vn.test(t)) return !1;
3777
3823
  const a = t.split("-")[0].toLowerCase();
3778
- return a.length === 2 ? dn.has(a) : a.length === 3 ? !un.has(a) : !1;
3824
+ return a.length === 2 ? gn.has(a) : a.length === 3 ? !fn.has(a) : !1;
3779
3825
  }
3780
- const pn = {
3826
+ const yn = {
3781
3827
  id: "html-lang-valid",
3782
3828
  wcag: ["3.1.1"],
3783
3829
  level: "A",
@@ -3787,7 +3833,7 @@ const pn = {
3787
3833
  run(t) {
3788
3834
  var e;
3789
3835
  const a = (e = t.documentElement.getAttribute("lang")) == null ? void 0 : e.trim();
3790
- return a && !Ae(a) ? [{
3836
+ return a && !ke(a) ? [{
3791
3837
  ruleId: "html-lang-valid",
3792
3838
  selector: "html",
3793
3839
  html: u(t.documentElement),
@@ -3796,7 +3842,7 @@ const pn = {
3796
3842
  }] : [];
3797
3843
  }
3798
3844
  };
3799
- function me(t) {
3845
+ function he(t) {
3800
3846
  var n;
3801
3847
  const a = t.ownerDocument.createTreeWalker(t, NodeFilter.SHOW_TEXT);
3802
3848
  let e;
@@ -3804,31 +3850,31 @@ function me(t) {
3804
3850
  if (!e.data.trim()) continue;
3805
3851
  const i = e.parentElement;
3806
3852
  if (!i || i instanceof HTMLElement && (i.hidden || i.style.display === "none")) continue;
3807
- let r = i, o = !1;
3808
- for (; r && r !== t; ) {
3809
- if (r.hasAttribute("lang")) {
3810
- o = !0;
3853
+ let o = i, r = !1;
3854
+ for (; o && o !== t; ) {
3855
+ if (o.hasAttribute("lang")) {
3856
+ r = !0;
3811
3857
  break;
3812
3858
  }
3813
- r = r.parentElement;
3859
+ o = o.parentElement;
3814
3860
  }
3815
- if (!o) return !0;
3861
+ if (!r) return !0;
3816
3862
  }
3817
3863
  for (const i of t.querySelectorAll("img[alt]")) {
3818
3864
  if (!((n = i.getAttribute("alt")) == null ? void 0 : n.trim())) continue;
3819
- let o = i.parentElement, s = !1;
3820
- for (; o && o !== t; ) {
3821
- if (o.hasAttribute("lang")) {
3865
+ let r = i.parentElement, s = !1;
3866
+ for (; r && r !== t; ) {
3867
+ if (r.hasAttribute("lang")) {
3822
3868
  s = !0;
3823
3869
  break;
3824
3870
  }
3825
- o = o.parentElement;
3871
+ r = r.parentElement;
3826
3872
  }
3827
3873
  if (!s) return !0;
3828
3874
  }
3829
3875
  return !1;
3830
3876
  }
3831
- const hn = {
3877
+ const wn = {
3832
3878
  id: "valid-lang",
3833
3879
  wcag: ["3.1.2"],
3834
3880
  level: "AA",
@@ -3841,7 +3887,7 @@ const hn = {
3841
3887
  if (b(e) || e === t.documentElement) continue;
3842
3888
  const n = e.getAttribute("lang"), i = n == null ? void 0 : n.trim();
3843
3889
  if (n && !i) {
3844
- me(e) && a.push({
3890
+ he(e) && a.push({
3845
3891
  ruleId: "valid-lang",
3846
3892
  selector: m(e),
3847
3893
  html: u(e),
@@ -3850,7 +3896,7 @@ const hn = {
3850
3896
  });
3851
3897
  continue;
3852
3898
  }
3853
- i && me(e) && (Ae(i) || a.push({
3899
+ i && he(e) && (ke(i) || a.push({
3854
3900
  ruleId: "valid-lang",
3855
3901
  selector: m(e),
3856
3902
  html: u(e),
@@ -3860,7 +3906,7 @@ const hn = {
3860
3906
  }
3861
3907
  return a;
3862
3908
  }
3863
- }, bn = {
3909
+ }, An = {
3864
3910
  id: "html-xml-lang-mismatch",
3865
3911
  wcag: ["3.1.1"],
3866
3912
  level: "A",
@@ -3868,11 +3914,11 @@ const hn = {
3868
3914
  guidance: "In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.",
3869
3915
  prompt: "Explain whether to remove xml:lang or align it with the lang value.",
3870
3916
  run(t) {
3871
- var i, r;
3872
- const a = t.documentElement, e = (i = a.getAttribute("lang")) == null ? void 0 : i.trim().toLowerCase(), n = (r = a.getAttribute("xml:lang")) == null ? void 0 : r.trim().toLowerCase();
3917
+ var i, o;
3918
+ const a = t.documentElement, e = (i = a.getAttribute("lang")) == null ? void 0 : i.trim().toLowerCase(), n = (o = a.getAttribute("xml:lang")) == null ? void 0 : o.trim().toLowerCase();
3873
3919
  if (e && n) {
3874
- const o = e.split("-")[0], s = n.split("-")[0];
3875
- if (o !== s)
3920
+ const r = e.split("-")[0], s = n.split("-")[0];
3921
+ if (r !== s)
3876
3922
  return [{
3877
3923
  ruleId: "html-xml-lang-mismatch",
3878
3924
  selector: "html",
@@ -3883,7 +3929,7 @@ const hn = {
3883
3929
  }
3884
3930
  return [];
3885
3931
  }
3886
- }, gn = {
3932
+ }, xn = {
3887
3933
  id: "td-headers-attr",
3888
3934
  wcag: ["1.3.1"],
3889
3935
  level: "A",
@@ -3896,25 +3942,25 @@ const hn = {
3896
3942
  if (b(e)) continue;
3897
3943
  const n = e.closest("table");
3898
3944
  if (!n) continue;
3899
- const i = e.getAttribute("id"), r = e.getAttribute("headers").split(/\s+/);
3900
- for (const o of r) {
3901
- if (o === i) {
3945
+ const i = e.getAttribute("id"), o = e.getAttribute("headers").split(/\s+/);
3946
+ for (const r of o) {
3947
+ if (r === i) {
3902
3948
  a.push({
3903
3949
  ruleId: "td-headers-attr",
3904
3950
  selector: m(e),
3905
3951
  html: u(e),
3906
3952
  impact: "serious",
3907
- message: `Headers attribute references the cell itself ("${o}").`
3953
+ message: `Headers attribute references the cell itself ("${r}").`
3908
3954
  });
3909
3955
  break;
3910
3956
  }
3911
- if (!n.querySelector(`th#${CSS.escape(o)}, td#${CSS.escape(o)}`)) {
3957
+ if (!n.querySelector(`th#${CSS.escape(r)}, td#${CSS.escape(r)}`)) {
3912
3958
  a.push({
3913
3959
  ruleId: "td-headers-attr",
3914
3960
  selector: m(e),
3915
3961
  html: u(e),
3916
3962
  impact: "serious",
3917
- message: `Headers attribute references non-existent ID "${o}".`
3963
+ message: `Headers attribute references non-existent ID "${r}".`
3918
3964
  });
3919
3965
  break;
3920
3966
  }
@@ -3922,7 +3968,7 @@ const hn = {
3922
3968
  }
3923
3969
  return a;
3924
3970
  }
3925
- }, fn = {
3971
+ }, kn = {
3926
3972
  id: "th-has-data-cells",
3927
3973
  wcag: ["1.3.1"],
3928
3974
  level: "A",
@@ -3944,7 +3990,7 @@ const hn = {
3944
3990
  }
3945
3991
  return a;
3946
3992
  }
3947
- }, vn = {
3993
+ }, Sn = {
3948
3994
  id: "td-has-header",
3949
3995
  wcag: ["1.3.1"],
3950
3996
  level: "A",
@@ -3956,16 +4002,16 @@ const hn = {
3956
4002
  const a = [];
3957
4003
  for (const i of t.querySelectorAll("table")) {
3958
4004
  if (b(i) || i.getAttribute("role") === "presentation" || i.getAttribute("role") === "none") continue;
3959
- const r = i.querySelectorAll("tr"), o = r.length;
4005
+ const o = i.querySelectorAll("tr"), r = o.length;
3960
4006
  let s = 0;
3961
- for (const d of r) {
4007
+ for (const d of o) {
3962
4008
  const h = d.querySelectorAll("td, th");
3963
4009
  let g = 0;
3964
4010
  for (const f of h)
3965
4011
  g += parseInt(f.getAttribute("colspan") || "1", 10);
3966
4012
  s = Math.max(s, g);
3967
4013
  }
3968
- if (o <= 3 && s <= 3) continue;
4014
+ if (r <= 3 && s <= 3) continue;
3969
4015
  const l = i.querySelector("th") !== null, p = i.querySelector("th[scope]") !== null, c = i.querySelector("td[headers]") !== null;
3970
4016
  if (l)
3971
4017
  for (const d of i.querySelectorAll("td")) {
@@ -3997,7 +4043,7 @@ const hn = {
3997
4043
  }
3998
4044
  return a;
3999
4045
  }
4000
- }, yn = {
4046
+ }, In = {
4001
4047
  id: "scope-attr-valid",
4002
4048
  wcag: ["1.3.1"],
4003
4049
  level: "A",
@@ -4009,18 +4055,18 @@ const hn = {
4009
4055
  const a = [], e = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
4010
4056
  for (const i of t.querySelectorAll("th[scope]")) {
4011
4057
  if (b(i)) continue;
4012
- const r = (n = i.getAttribute("scope")) == null ? void 0 : n.toLowerCase();
4013
- r && !e.has(r) && a.push({
4058
+ const o = (n = i.getAttribute("scope")) == null ? void 0 : n.toLowerCase();
4059
+ o && !e.has(o) && a.push({
4014
4060
  ruleId: "scope-attr-valid",
4015
4061
  selector: m(i),
4016
4062
  html: u(i),
4017
4063
  impact: "moderate",
4018
- message: `Invalid scope value "${r}". Use row, col, rowgroup, or colgroup.`
4064
+ message: `Invalid scope value "${o}". Use row, col, rowgroup, or colgroup.`
4019
4065
  });
4020
4066
  }
4021
4067
  return a;
4022
4068
  }
4023
- }, wn = {
4069
+ }, En = {
4024
4070
  id: "empty-table-header",
4025
4071
  wcag: [],
4026
4072
  level: "A",
@@ -4043,7 +4089,7 @@ const hn = {
4043
4089
  }
4044
4090
  return a;
4045
4091
  }
4046
- }, F = ["aria-labelledby", "aria-describedby", "aria-controls", "aria-owns", "aria-flowto"], An = {
4092
+ }, W = ["aria-labelledby", "aria-describedby", "aria-controls", "aria-owns", "aria-flowto"], qn = {
4047
4093
  id: "duplicate-id-aria",
4048
4094
  wcag: ["4.1.2"],
4049
4095
  level: "A",
@@ -4053,25 +4099,25 @@ const hn = {
4053
4099
  run(t) {
4054
4100
  const a = [], e = /* @__PURE__ */ new Set();
4055
4101
  for (const i of t.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))
4056
- for (const r of F) {
4057
- const o = i.getAttribute(r);
4058
- o && o.split(/\s+/).forEach((s) => e.add(s));
4102
+ for (const o of W) {
4103
+ const r = i.getAttribute(o);
4104
+ r && r.split(/\s+/).forEach((s) => e.add(s));
4059
4105
  }
4060
4106
  for (const i of t.querySelectorAll("label[for]")) {
4061
- const r = i.getAttribute("for");
4062
- r && e.add(r);
4107
+ const o = i.getAttribute("for");
4108
+ o && e.add(o);
4063
4109
  }
4064
4110
  const n = /* @__PURE__ */ new Map();
4065
4111
  for (const i of t.querySelectorAll("[id]"))
4066
4112
  e.has(i.id) && (i instanceof HTMLElement && (i.style.display === "none" || i.style.visibility === "hidden" || i.hidden) || n.set(i.id, (n.get(i.id) ?? 0) + 1));
4067
- for (const [i, r] of n) {
4068
- if (r <= 1) continue;
4069
- const o = t.querySelectorAll(`#${CSS.escape(i)}`), s = t.querySelector(
4070
- F.map((c) => `[${c}~="${CSS.escape(i)}"]`).join(", ")
4113
+ for (const [i, o] of n) {
4114
+ if (o <= 1) continue;
4115
+ const r = t.querySelectorAll(`#${CSS.escape(i)}`), s = t.querySelector(
4116
+ W.map((c) => `[${c}~="${CSS.escape(i)}"]`).join(", ")
4071
4117
  ), l = t.querySelector(`label[for="${CSS.escape(i)}"]`);
4072
4118
  let p;
4073
4119
  if (s) {
4074
- const c = F.find(
4120
+ const c = W.find(
4075
4121
  (d) => {
4076
4122
  var h;
4077
4123
  return (h = s.getAttribute(d)) == null ? void 0 : h.split(/\s+/).includes(i);
@@ -4081,17 +4127,17 @@ const hn = {
4081
4127
  } else l && (p = "label[for]");
4082
4128
  a.push({
4083
4129
  ruleId: "duplicate-id-aria",
4084
- selector: m(o[1]),
4085
- html: u(o[1]),
4130
+ selector: m(r[1]),
4131
+ html: u(r[1]),
4086
4132
  impact: "critical",
4087
4133
  message: `Duplicate ID "${i}" referenced by ${p ?? "an accessibility attribute"}.`,
4088
- context: `First element: ${u(o[0])}${p ? `
4134
+ context: `First element: ${u(r[0])}${p ? `
4089
4135
  Referenced by: ${p}` : ""}`
4090
4136
  });
4091
4137
  }
4092
4138
  return a;
4093
4139
  }
4094
- }, xn = {
4140
+ }, Ln = {
4095
4141
  id: "video-caption",
4096
4142
  wcag: ["1.2.2"],
4097
4143
  level: "A",
@@ -4112,7 +4158,7 @@ Referenced by: ${p}` : ""}`
4112
4158
  }
4113
4159
  return a;
4114
4160
  }
4115
- }, kn = {
4161
+ }, Tn = {
4116
4162
  id: "audio-caption",
4117
4163
  wcag: ["1.2.1"],
4118
4164
  level: "A",
@@ -4134,7 +4180,7 @@ Referenced by: ${p}` : ""}`
4134
4180
  }
4135
4181
  return a;
4136
4182
  }
4137
- }, Sn = /* @__PURE__ */ new Set([
4183
+ }, Rn = /* @__PURE__ */ new Set([
4138
4184
  "SCRIPT",
4139
4185
  "STYLE",
4140
4186
  "NOSCRIPT",
@@ -4150,25 +4196,25 @@ Referenced by: ${p}` : ""}`
4150
4196
  "BR",
4151
4197
  "HR"
4152
4198
  ]);
4153
- function pe([t, a, e]) {
4199
+ function be([t, a, e]) {
4154
4200
  return "#" + [t, a, e].map((n) => n.toString(16).padStart(2, "0")).join("");
4155
4201
  }
4156
- function In(t) {
4202
+ function Cn(t) {
4157
4203
  return t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement || t instanceof HTMLButtonElement ? t.disabled : !!(t.closest("fieldset[disabled]") || t.getAttribute("aria-disabled") === "true");
4158
4204
  }
4159
- function qn(t, a) {
4205
+ function Nn(t, a) {
4160
4206
  if (t.tagName !== "LABEL") return !1;
4161
4207
  const e = t, n = e.htmlFor;
4162
4208
  if (n) {
4163
- const o = a.getElementById(n);
4164
- if (o && (o.disabled || o.getAttribute("aria-disabled") === "true")) return !0;
4209
+ const r = a.getElementById(n);
4210
+ if (r && (r.disabled || r.getAttribute("aria-disabled") === "true")) return !0;
4165
4211
  }
4166
4212
  const i = e.querySelector("input, select, textarea, button");
4167
4213
  if (i && (i.disabled || i.getAttribute("aria-disabled") === "true")) return !0;
4168
- const r = e.id;
4169
- return !!(r && a.querySelector(`[aria-labelledby~="${r}"][aria-disabled="true"]`));
4214
+ const o = e.id;
4215
+ return !!(o && a.querySelector(`[aria-labelledby~="${o}"][aria-disabled="true"]`));
4170
4216
  }
4171
- function Ln(t) {
4217
+ function Mn(t) {
4172
4218
  const a = t.clip;
4173
4219
  if (a && a.startsWith("rect(")) {
4174
4220
  const n = a.match(/[\d.]+/g);
@@ -4182,17 +4228,17 @@ function Ln(t) {
4182
4228
  }
4183
4229
  return !1;
4184
4230
  }
4185
- function En(t) {
4231
+ function Dn(t) {
4186
4232
  if (b(t)) return !0;
4187
4233
  let a = t;
4188
4234
  for (; a; ) {
4189
4235
  const e = w(a);
4190
- if (e.display === "none" || e.visibility === "hidden" || Ln(e)) return !0;
4236
+ if (e.display === "none" || e.visibility === "hidden" || Mn(e)) return !0;
4191
4237
  a = a.parentElement;
4192
4238
  }
4193
4239
  return !1;
4194
4240
  }
4195
- function Tn(t) {
4241
+ function $n(t) {
4196
4242
  let a = 1, e = t;
4197
4243
  for (; e; ) {
4198
4244
  const n = w(e), i = parseFloat(n.opacity);
@@ -4200,7 +4246,7 @@ function Tn(t) {
4200
4246
  }
4201
4247
  return a;
4202
4248
  }
4203
- const Cn = {
4249
+ const Hn = {
4204
4250
  grayscale: 0,
4205
4251
  blur: 0,
4206
4252
  "hue-rotate": 0,
@@ -4211,37 +4257,37 @@ const Cn = {
4211
4257
  saturate: 1,
4212
4258
  opacity: 1
4213
4259
  };
4214
- function Rn(t) {
4260
+ function Pn(t) {
4215
4261
  const a = parseFloat(t);
4216
4262
  return isNaN(a) ? NaN : t.trim().endsWith("%") ? a / 100 : a;
4217
4263
  }
4218
- const he = /([a-z-]+)\(([^)]*)\)/g;
4219
- function be(t) {
4264
+ const ge = /([a-z-]+)\(([^)]*)\)/g;
4265
+ function fe(t) {
4220
4266
  let a, e = !1;
4221
- for (he.lastIndex = 0; a = he.exec(t); ) {
4267
+ for (ge.lastIndex = 0; a = ge.exec(t); ) {
4222
4268
  e = !0;
4223
- const n = Cn[a[1]];
4224
- if (n === void 0 || Rn(a[2]) !== n) return !1;
4269
+ const n = Hn[a[1]];
4270
+ if (n === void 0 || Pn(a[2]) !== n) return !1;
4225
4271
  }
4226
4272
  return e;
4227
4273
  }
4228
- function Nn(t) {
4274
+ function zn(t) {
4229
4275
  let a = t;
4230
4276
  for (; a; ) {
4231
4277
  const e = w(a), n = e.filter;
4232
- if (n && n !== "none" && n !== "initial" && !be(n)) return !0;
4278
+ if (n && n !== "none" && n !== "initial" && !fe(n)) return !0;
4233
4279
  const i = e.mixBlendMode;
4234
4280
  if (i && i !== "normal" && i !== "initial") return !0;
4235
- const r = e.backdropFilter;
4236
- if (r && r !== "none" && r !== "initial" && !be(r)) return !0;
4281
+ const o = e.backdropFilter;
4282
+ if (o && o !== "none" && o !== "initial" && !fe(o)) return !0;
4237
4283
  a = a.parentElement;
4238
4284
  }
4239
4285
  return !1;
4240
4286
  }
4241
- function Mn(t) {
4287
+ function Fn(t) {
4242
4288
  return t.closest("select") !== null;
4243
4289
  }
4244
- const $n = {
4290
+ const jn = {
4245
4291
  id: "color-contrast",
4246
4292
  wcag: ["1.4.3"],
4247
4293
  level: "AA",
@@ -4252,174 +4298,174 @@ const $n = {
4252
4298
  const a = [], e = t.body;
4253
4299
  if (!e) return [];
4254
4300
  const n = t.createTreeWalker(e, NodeFilter.SHOW_TEXT), i = /* @__PURE__ */ new Set();
4255
- let r;
4256
- for (; r = n.nextNode(); ) {
4257
- if (!r.textContent || !r.textContent.trim()) continue;
4258
- const o = r.parentElement;
4259
- if (!o || i.has(o) || (i.add(o), Sn.has(o.tagName))) continue;
4260
- const s = o.tagName;
4261
- if (s === "BODY" || s === "HTML" || Mn(o) || In(o) || qn(o, t) || En(o)) continue;
4262
- const l = w(o);
4263
- if (parseFloat(l.opacity) === 0 || Tn(o) < 0.1) continue;
4301
+ let o;
4302
+ for (; o = n.nextNode(); ) {
4303
+ if (!o.textContent || !o.textContent.trim()) continue;
4304
+ const r = o.parentElement;
4305
+ if (!r || i.has(r) || (i.add(r), Rn.has(r.tagName))) continue;
4306
+ const s = r.tagName;
4307
+ if (s === "BODY" || s === "HTML" || Fn(r) || Cn(r) || Nn(r, t) || Dn(r)) continue;
4308
+ const l = w(r);
4309
+ if (parseFloat(l.opacity) === 0 || $n(r) < 0.1) continue;
4264
4310
  const p = l.textShadow;
4265
- if (p && p !== "none" && p !== "initial" || Nn(o)) continue;
4266
- const c = $(l.color);
4311
+ if (p && p !== "none" && p !== "initial" || zn(r)) continue;
4312
+ const c = D(l.color);
4267
4313
  if (!c) continue;
4268
4314
  const d = l.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || l.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
4269
- if (d && (d[1].endsWith("%") ? parseFloat(d[1]) / 100 : parseFloat(d[1])) === 0 || Ve(o)) continue;
4270
- const h = Be(o);
4315
+ if (d && (d[1].endsWith("%") ? parseFloat(d[1]) / 100 : parseFloat(d[1])) === 0 || Ye(r)) continue;
4316
+ const h = Ve(r);
4271
4317
  if (!h) continue;
4272
- const g = N(c[0], c[1], c[2]), f = N(h[0], h[1], h[2]), y = ve(g, f), x = Xe(o) ? 3 : 4.5;
4318
+ const g = M(c[0], c[1], c[2]), f = M(h[0], h[1], h[2]), y = we(g, f), x = Je(r) ? 3 : 4.5;
4273
4319
  if (y < x) {
4274
- const k = Math.round(y * 100) / 100, D = pe(c), Ie = pe(h);
4320
+ const k = Math.round(y * 100) / 100, P = be(c), qe = be(h);
4275
4321
  a.push({
4276
4322
  ruleId: "color-contrast",
4277
- selector: m(o),
4278
- html: u(o),
4323
+ selector: m(r),
4324
+ html: u(r),
4279
4325
  impact: "serious",
4280
4326
  message: `Insufficient color contrast ratio of ${k}:1 (required ${x}:1).`,
4281
- context: `foreground: ${D} rgb(${c.join(", ")}), background: ${Ie} rgb(${h.join(", ")}), ratio: ${k}:1, required: ${x}:1`
4327
+ context: `foreground: ${P} rgb(${c.join(", ")}), background: ${qe} rgb(${h.join(", ")}), ratio: ${k}:1, required: ${x}:1`
4282
4328
  });
4283
4329
  }
4284
4330
  }
4285
4331
  return a;
4286
4332
  }
4287
- }, xe = [
4333
+ }, Se = [
4288
4334
  // Document Structure
4289
- ra,
4290
- oa,
4291
- sa,
4292
- la,
4293
- ca,
4294
4335
  ua,
4295
4336
  ma,
4337
+ pa,
4296
4338
  ha,
4297
- ga,
4339
+ ba,
4340
+ fa,
4341
+ va,
4342
+ wa,
4343
+ xa,
4298
4344
  // Images
4299
- Je,
4300
- Ze,
4301
- et,
4302
- tt,
4303
4345
  nt,
4304
- it,
4305
4346
  ot,
4347
+ rt,
4306
4348
  st,
4307
- ut,
4308
- // Forms
4309
- gt,
4349
+ ct,
4350
+ dt,
4351
+ mt,
4352
+ pt,
4310
4353
  ft,
4311
- vt,
4312
- yt,
4313
- qt,
4314
- Lt,
4315
- Et,
4316
- // Keyboard
4317
- Ct,
4354
+ // Forms
4355
+ xt,
4356
+ kt,
4357
+ St,
4358
+ It,
4318
4359
  Nt,
4319
- Wt,
4320
- Ft,
4321
- Pt,
4322
- // Structure
4323
- Ot,
4324
- da,
4325
- fa,
4326
- Bt,
4327
- Ut,
4328
- _t,
4360
+ Mt,
4361
+ Dt,
4362
+ // Keyboard
4363
+ Ht,
4364
+ zt,
4329
4365
  Vt,
4366
+ _t,
4330
4367
  Gt,
4368
+ // Structure
4331
4369
  Yt,
4370
+ ga,
4371
+ ka,
4332
4372
  Xt,
4333
4373
  Kt,
4334
4374
  Jt,
4335
4375
  Qt,
4376
+ Zt,
4336
4377
  ea,
4337
- ia,
4338
4378
  ta,
4379
+ aa,
4339
4380
  na,
4381
+ ia,
4382
+ ra,
4383
+ da,
4384
+ sa,
4385
+ ca,
4340
4386
  // ARIA
4341
- va,
4342
- ya,
4343
- wa,
4344
- xa,
4387
+ Sa,
4388
+ Ia,
4389
+ Ea,
4345
4390
  La,
4346
- Ca,
4347
4391
  Ma,
4348
- $a,
4349
- za,
4350
- ja,
4351
- Wa,
4392
+ Ha,
4352
4393
  Fa,
4353
- Pa,
4354
- Oa,
4394
+ ja,
4355
4395
  Ba,
4356
- Ua,
4357
- _a,
4396
+ Oa,
4358
4397
  Va,
4398
+ _a,
4359
4399
  Ga,
4400
+ Ya,
4401
+ Xa,
4360
4402
  Ka,
4361
- Sa,
4362
4403
  Ja,
4363
- // Links
4404
+ Qa,
4364
4405
  Za,
4365
- en,
4366
- ln,
4367
- // Language
4368
- cn,
4369
- pn,
4406
+ an,
4407
+ Ra,
4408
+ nn,
4409
+ // Links
4410
+ rn,
4411
+ sn,
4370
4412
  hn,
4413
+ // Language
4371
4414
  bn,
4372
- // Tables
4373
- gn,
4374
- fn,
4375
- vn,
4376
4415
  yn,
4377
4416
  wn,
4378
- // Parsing
4379
4417
  An,
4380
- // Media
4418
+ // Tables
4381
4419
  xn,
4382
4420
  kn,
4421
+ Sn,
4422
+ In,
4423
+ En,
4424
+ // Parsing
4425
+ qn,
4426
+ // Media
4427
+ Ln,
4428
+ Tn,
4383
4429
  // Color
4384
- $n
4430
+ jn
4385
4431
  ];
4386
- let K = [], ke = /* @__PURE__ */ new Set(), M, C;
4387
- function Wn(t) {
4388
- t.additionalRules && (K = t.additionalRules), t.disabledRules && (ke = new Set(t.disabledRules)), "locale" in t && (M = t.locale || void 0), C = void 0;
4432
+ let Q = [], Ie = /* @__PURE__ */ new Set(), I, C;
4433
+ function Vn(t) {
4434
+ t.additionalRules && (Q = t.additionalRules), t.disabledRules && (Ie = new Set(t.disabledRules)), "locale" in t && (I = t.locale || void 0), C = void 0;
4389
4435
  }
4390
- function J() {
4436
+ function Z() {
4391
4437
  if (C) return C;
4392
- const a = xe.filter((e) => !ke.has(e.id)).concat(K);
4393
- return M ? (C = Ke(a, M), C) : a;
4438
+ const a = Se.filter((e) => !Ie.has(e.id)).concat(Q);
4439
+ return I ? (C = Qe(a, I), C) : a;
4394
4440
  }
4395
- function Fn(t) {
4396
- Se();
4397
- const a = J(), e = [];
4398
- let n = 0;
4441
+ function _n(t) {
4442
+ Ee();
4443
+ const a = Z(), e = I, n = [];
4444
+ let i = 0;
4399
4445
  return {
4400
- processChunk(i) {
4446
+ processChunk(o) {
4401
4447
  const r = performance.now();
4402
- for (; n < a.length; ) {
4448
+ for (; i < a.length; ) {
4403
4449
  try {
4404
- e.push(...a[n].run(t));
4450
+ n.push(...a[i].run(t));
4405
4451
  } catch {
4406
4452
  }
4407
- if (n++, performance.now() - r >= i) break;
4453
+ if (i++, performance.now() - r >= o) break;
4408
4454
  }
4409
- return n < a.length;
4455
+ return i < a.length;
4410
4456
  },
4411
4457
  getViolations() {
4412
- return e;
4458
+ return e ? Ae(n, e) : n;
4413
4459
  }
4414
4460
  };
4415
4461
  }
4416
- function Se() {
4417
- Re(), qe(), Le(), Oe(), Pe(), Ne();
4462
+ function Ee() {
4463
+ Me(), Le(), Te(), Oe(), Be(), De();
4418
4464
  }
4419
- function Pn(t) {
4465
+ function Gn(t) {
4420
4466
  var n;
4421
- Se();
4422
- const a = J(), e = [];
4467
+ Ee();
4468
+ const a = Z(), e = [];
4423
4469
  for (const i of a)
4424
4470
  try {
4425
4471
  e.push(...i.run(t));
@@ -4428,214 +4474,215 @@ function Pn(t) {
4428
4474
  return {
4429
4475
  url: ((n = t.location) == null ? void 0 : n.href) ?? "",
4430
4476
  timestamp: Date.now(),
4431
- violations: e,
4477
+ violations: I ? Ae(e, I) : e,
4432
4478
  ruleCount: a.length
4433
4479
  };
4434
4480
  }
4435
- const Hn = new Map(xe.map((t) => [t.id, t]));
4436
- function On(t) {
4437
- if (M)
4438
- return J().find((n) => n.id === t);
4439
- const a = Hn.get(t);
4440
- return a || K.find((e) => e.id === t);
4481
+ const Wn = new Map(Se.map((t) => [t.id, t]));
4482
+ function Yn(t) {
4483
+ if (I)
4484
+ return Z().find((n) => n.id === t);
4485
+ const a = Wn.get(t);
4486
+ return a || Q.find((e) => e.id === t);
4441
4487
  }
4442
- const Bn = {
4443
- "document-title": { description: "Documents must have a <title> element to provide users with an overview of content.", guidance: "Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp')." },
4444
- bypass: { description: "Page must have a mechanism to bypass repeated blocks of content.", guidance: 'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.' },
4445
- "page-has-heading-one": { description: "Page should contain a level-one heading.", guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title." },
4446
- "frame-title": { description: "Frames must have an accessible name.", guidance: "Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'." },
4447
- "frame-title-unique": { description: "Frame titles should be unique.", guidance: "When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content." },
4448
- "meta-viewport": { description: "Viewport meta tag must not disable user scaling.", guidance: "Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom." },
4449
- "meta-refresh": { description: "Meta refresh must not redirect or refresh automatically.", guidance: "Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls." },
4450
- blink: { description: "The <blink> element must not be used.", guidance: "Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons." },
4451
- marquee: { description: "The <marquee> element must not be used.", guidance: "Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds." },
4452
- "img-alt": { description: `Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`, guidance: "Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead." },
4453
- "svg-img-alt": { description: "SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.", guidance: "Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead." },
4454
- "input-image-alt": { description: 'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.', guidance: "Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image." },
4455
- "image-redundant-alt": { description: "Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.", guidance: "When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition." },
4456
- "image-alt-redundant-words": { description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.", guidance: "Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'." },
4457
- "area-alt": { description: "Image map <area> elements must have alternative text.", guidance: "Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links." },
4458
- "object-alt": { description: "<object> elements must have alternative text.", guidance: "Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name." },
4459
- "role-img-alt": { description: "Elements with role='img' must have an accessible name.", guidance: "When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead." },
4460
- "server-side-image-map": { description: "Server-side image maps must not be used.", guidance: "Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead." },
4461
- label: { description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.", guidance: "Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing." },
4462
- "form-field-multiple-labels": { description: "Form fields should not have multiple label elements.", guidance: "When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information." },
4463
- "select-name": { description: "Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.", guidance: "Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label." },
4464
- "input-button-name": { description: "Input buttons must have discernible text via value, aria-label, or aria-labelledby.", guidance: "Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name." },
4465
- "autocomplete-valid": { description: "Autocomplete attribute must use valid values from the HTML specification.", guidance: "The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens." },
4466
- "label-content-name-mismatch": { description: "Interactive elements with visible text must have accessible names that contain that text.", guidance: "For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text." },
4467
- "label-title-only": { description: "Form elements should not use title attribute as the only accessible name.", guidance: "The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it." },
4468
- tabindex: { description: "Elements should not have tabindex greater than 0, which disrupts natural tab order.", guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence." },
4469
- "focus-order-semantics": { description: "Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.", guidance: "When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead." },
4470
- "nested-interactive": { description: "Interactive controls must not be nested inside each other.", guidance: "Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting." },
4471
- "scrollable-region-focusable": { description: "Scrollable regions must be keyboard accessible.", guidance: "Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content." },
4472
- accesskeys: { description: "Accesskey attribute values must be unique.", guidance: "When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly." },
4473
- "heading-order": { description: "Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.", guidance: "Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section." },
4474
- "empty-heading": { description: "Headings must have discernible text.", guidance: "Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements." },
4475
- "p-as-heading": { description: "Paragraphs should not be styled to look like headings.", guidance: "When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy." },
4476
- "landmark-one-main": { description: "Page should have exactly one main landmark.", guidance: "The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation." },
4477
- "landmark-no-duplicate-banner": { description: "Page should not have more than one banner landmark.", guidance: "The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners." },
4478
- "landmark-no-duplicate-contentinfo": { description: "Page should not have more than one contentinfo landmark.", guidance: "The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them." },
4479
- "landmark-no-duplicate-main": { description: "Page should not have more than one main landmark.", guidance: "Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements." },
4480
- "landmark-banner-is-top-level": { description: "Banner landmark should not be nested within another landmark.", guidance: "The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page." },
4481
- "landmark-contentinfo-is-top-level": { description: "Contentinfo landmark should not be nested within another landmark.", guidance: "The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements." },
4482
- "landmark-main-is-top-level": { description: "Main landmark should not be nested within another landmark.", guidance: "The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements." },
4483
- "landmark-complementary-is-top-level": { description: "Aside (complementary) landmark should be top-level or directly inside main.", guidance: "The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks." },
4484
- "landmark-unique": { description: "Landmarks should have unique labels when there are multiple of the same type.", guidance: "When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation')." },
4485
- region: { description: "All page content should be contained within landmarks.", guidance: "Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks." },
4486
- list: { description: "<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.", guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly." },
4487
- listitem: { description: "<li> elements must be contained in a <ul>, <ol>, or <menu>.", guidance: "List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container." },
4488
- dlitem: { description: "<dt> and <dd> elements must be contained in a <dl>.", guidance: "Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies." },
4489
- "definition-list": { description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.", guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or restructure using proper definition list markup." },
4490
- "aria-roles": { description: "ARIA role values must be valid.", guidance: "Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem." },
4491
- "aria-valid-attr": { description: "ARIA attributes must be valid (correctly spelled).", guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+)." },
4492
- "aria-valid-attr-value": { description: "ARIA attributes must have valid values.", guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs." },
4493
- "aria-required-attr": { description: "Elements with ARIA roles must have all required ARIA attributes.", guidance: "Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value." },
4494
- "aria-allowed-attr": { description: "ARIA attributes must be allowed for the element's role.", guidance: "Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed." },
4495
- "aria-allowed-role": { description: "ARIA role must be appropriate for the element.", guidance: "Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element." },
4496
- "aria-required-children": { description: "Certain ARIA roles require specific child roles to be present.", guidance: "Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>)." },
4497
- "aria-required-parent": { description: "Certain ARIA roles must be contained within specific parent roles.", guidance: "Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>)." },
4498
- "aria-hidden-body": { description: "aria-hidden='true' must not be present on the document body.", guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead." },
4499
- "aria-hidden-focus": { description: "Elements with aria-hidden='true' must not contain focusable elements.", guidance: "When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden." },
4500
- "aria-command-name": { description: "ARIA commands must have an accessible name.", guidance: "Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name." },
4501
- "aria-input-field-name": { description: "ARIA input fields must have an accessible name.", guidance: "ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible." },
4502
- "aria-toggle-field-name": { description: "ARIA toggle fields must have an accessible name.", guidance: "ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label." },
4503
- "aria-meter-name": { description: "ARIA meter elements must have an accessible name.", guidance: "Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context." },
4504
- "aria-progressbar-name": { description: "ARIA progressbar elements must have an accessible name.", guidance: "Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label." },
4505
- "aria-dialog-name": { description: "ARIA dialogs must have an accessible name.", guidance: "Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name." },
4506
- "aria-tooltip-name": { description: "ARIA tooltips must have an accessible name.", guidance: "Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label." },
4507
- "aria-treeitem-name": { description: "ARIA treeitem elements must have an accessible name.", guidance: "Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem." },
4508
- "aria-prohibited-attr": { description: "ARIA attributes must not be prohibited for the element's role.", guidance: "Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role." },
4509
- "presentation-role-conflict": { description: "Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.", guidance: "When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes." },
4510
- "button-name": { description: "Buttons must have discernible text.", guidance: "Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action." },
4511
- "summary-name": { description: "<summary> elements must have an accessible name.", guidance: "The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section." },
4512
- "link-name": { description: "Links must have discernible text via content, aria-label, or aria-labelledby.", guidance: "Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context." },
4513
- "skip-link": { description: "Skip links must point to a valid target on the page.", guidance: "Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS." },
4514
- "link-in-text-block": { description: "Links within text blocks must be distinguishable by more than color alone.", guidance: "Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover." },
4515
- "html-has-lang": { description: "The <html> element must have a lang attribute.", guidance: "Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page (e.g., lang='en' for English, lang='es' for Spanish)." },
4516
- "html-lang-valid": { description: "The lang attribute on <html> must have a valid value.", guidance: "The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content." },
4517
- "valid-lang": { description: "The lang attribute must have a valid value on all elements.", guidance: "When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru." },
4518
- "html-xml-lang-mismatch": { description: "The lang and xml:lang attributes on <html> must match.", guidance: "In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values." },
4519
- "td-headers-attr": { description: "All cells in a table using headers attribute must reference valid header IDs.", guidance: "The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead." },
4520
- "th-has-data-cells": { description: "Table headers should be associated with data cells.", guidance: "A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data." },
4521
- "td-has-header": { description: "Data cells in tables larger than 3x3 should have associated headers.", guidance: "In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear." },
4522
- "scope-attr-valid": { description: "The scope attribute on table headers must have a valid value.", guidance: "The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells." },
4523
- "empty-table-header": { description: "Table header cells should have visible text.", guidance: "Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label." },
4524
- "duplicate-id-aria": { description: "IDs used in ARIA and label associations must be unique to avoid broken references.", guidance: "When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document." },
4525
- "video-caption": { description: "Video elements must have captions via <track kind='captions'>.", guidance: "Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects." },
4526
- "audio-caption": { description: "Audio elements should have a text alternative or transcript.", guidance: "Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds." },
4527
- "color-contrast": { description: "Text elements must have sufficient color contrast against the background.", guidance: "WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa." }
4528
- }, Un = {
4529
- "document-title": { description: "Los documentos deben tener un elemento <title> para proporcionar a los usuarios una vista general del contenido.", guidance: "Los usuarios de lectores de pantalla dependen de los títulos de página para identificar y navegar entre pestañas/ventanas. Agregue un elemento <title> descriptivo en <head> que resuma el propósito de la página. Mantenga los títulos únicos en todo el sitio, colocando el contenido específico antes del nombre del sitio (por ejemplo, 'Contáctenos - Acme Corp')." },
4530
- bypass: { description: "La página debe tener un mecanismo para omitir bloques de contenido repetidos.", guidance: 'Los usuarios de teclado deben poder omitir contenido repetitivo como la navegación. Proporcione un enlace de salto en la parte superior de la página que enlace al contenido principal (por ejemplo, <a href="#main">Saltar al contenido principal</a>), o use un landmark <main>. Los lectores de pantalla pueden saltar directamente a los landmarks, por lo que un elemento <main> correctamente marcado satisface este requisito.' },
4531
- "page-has-heading-one": { description: "La página debe contener un encabezado de nivel uno.", guidance: "Un encabezado de nivel uno (<h1> o role='heading' con aria-level='1') ayuda a los usuarios a comprender el tema de la página y proporciona un punto de referencia para la navegación con lector de pantalla. Cada página debe tener exactamente un h1 que describa el contenido principal, típicamente coincidiendo o similar al título de la página." },
4532
- "frame-title": { description: "Los marcos deben tener un nombre accesible.", guidance: "Los lectores de pantalla anuncian los títulos de los marcos cuando los usuarios navegan por ellos. Agregue un atributo title a los elementos <iframe> y <frame> que describa el propósito del marco (por ejemplo, <iframe title='Reproductor de video'>). Evite títulos genéricos como 'marco' o 'iframe'. Si el marco es decorativo, use aria-hidden='true'." },
4533
- "frame-title-unique": { description: "Los títulos de los marcos deben ser únicos.", guidance: "Cuando varios marcos tienen títulos idénticos, los usuarios de lectores de pantalla no pueden distinguirlos. Dé a cada marco un título único y descriptivo que explique su propósito específico o contenido." },
4534
- "meta-viewport": { description: "La etiqueta meta viewport no debe deshabilitar el zoom del usuario.", guidance: "Los usuarios con baja visión necesitan ampliar el contenido al 200% o más. Establecer user-scalable=no o maximum-scale=1 impide el zoom y no cumple con WCAG. Elimine estas restricciones. Si su diseño se rompe con zoom alto, corrija el diseño responsivo en lugar de impedir el zoom." },
4535
- "meta-refresh": { description: "La etiqueta meta refresh no debe redirigir o actualizar automáticamente.", guidance: "Las actualizaciones o redirecciones automáticas de página pueden desorientar a los usuarios, especialmente aquellos que usan lectores de pantalla o con discapacidades cognitivas. Pueden perder su lugar o no tener tiempo para leer el contenido. Si se necesita una redirección, use una redirección del lado del servidor (HTTP 301/302). Para actualizaciones temporizadas, proporcione controles al usuario." },
4536
- blink: { description: "El elemento <blink> no debe usarse.", guidance: "El contenido parpadeante puede causar convulsiones en usuarios con epilepsia fotosensible y es una distracción para usuarios con trastornos de atención. El elemento <blink> está obsoleto y nunca debe usarse. Si necesita llamar la atención sobre el contenido, use métodos menos intrusivos como color, bordes o iconos." },
4537
- marquee: { description: "El elemento <marquee> no debe usarse.", guidance: "El contenido que se desplaza o se mueve es difícil de leer para muchos usuarios, especialmente aquellos con discapacidades cognitivas o visuales. El elemento <marquee> está obsoleto. Reemplace el texto en movimiento con contenido estático. Si el contenido debe desplazarse, proporcione controles de pausa/detención y asegúrese de que se detenga después de 5 segundos." },
4538
- "img-alt": { description: `Las imágenes deben tener texto alternativo. Agregue un atributo alt a los elementos <img>. Las imágenes decorativas pueden usar un atributo alt vacío (alt=""), role='none' o role='presentation'.`, guidance: "Cada imagen necesita un atributo alt. Para imágenes informativas, describa el contenido o la función de manera concisa. Para imágenes decorativas (fondos, espaciadores, adornos puramente visuales), use alt='' para ocultarlas de los lectores de pantalla. Nunca omita alt por completo: los lectores de pantalla pueden leer el nombre del archivo en su lugar." },
4539
- "svg-img-alt": { description: "Los elementos SVG con rol img, graphics-document o graphics-symbol deben tener un nombre accesible mediante un elemento <title>, aria-label o aria-labelledby.", guidance: "Los SVG en línea con role='img' necesitan nombres accesibles. Agregue un elemento <title> como primer hijo del SVG (los lectores de pantalla lo anunciarán), o use aria-label en el elemento SVG. Para SVGs complejos, use aria-labelledby haciendo referencia tanto a un elemento <title> como a un elemento <desc>. Los SVGs decorativos deben usar aria-hidden='true' en su lugar." },
4540
- "input-image-alt": { description: 'Las entradas de imagen (<input type="image">) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.', guidance: "Los botones de imagen (<input type='image'>) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen." },
4541
- "image-redundant-alt": { description: "El texto alternativo de la imagen no debe duplicar el texto del enlace o botón adyacente. Cuando el texto alt repite el texto circundante, los usuarios de lectores de pantalla escuchan la misma información dos veces.", guidance: "Cuando una imagen está dentro de un enlace o botón que también tiene texto, haga que el texto alt sea complementario en lugar de idéntico. Si la imagen es puramente decorativa en ese contexto, use alt='' para evitar la repetición." },
4542
- "image-alt-redundant-words": { description: "El texto alternativo de la imagen no debe contener palabras como 'imagen', 'foto' o 'fotografía': los lectores de pantalla ya anuncian el tipo de elemento.", guidance: "Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'." },
4543
- "area-alt": { description: "Los elementos <area> del mapa de imagen deben tener texto alternativo.", guidance: "Cada región clicable en un mapa de imagen necesita texto alternativo para que los usuarios de lectores de pantalla sepan qué representa la región. Agregue un atributo alt a cada elemento <area> describiendo su propósito. Para mapas de imagen complejos, considere usar enfoques alternativos como SVG con enlaces incrustados, o una lista de enlaces de texto." },
4544
- "object-alt": { description: "Los elementos <object> deben tener texto alternativo.", guidance: "Los elementos object incrustan contenido externo que puede no ser accesible para todos los usuarios. Proporcione texto alternativo mediante aria-label, aria-labelledby o un atributo title. El contenido de respaldo dentro de <object> solo se muestra cuando el objeto no se carga y no sirve como nombre accesible." },
4545
- "role-img-alt": { description: "Los elementos con role='img' deben tener un nombre accesible.", guidance: "Cuando asigna role='img' a un elemento (como un div que contiene fuentes de iconos o fondos CSS), debe proporcionar un nombre accesible mediante aria-label o aria-labelledby. Sin esto, los usuarios de lectores de pantalla no tienen forma de entender lo que representa la imagen. Si la imagen es decorativa, use role='presentation' o role='none' en su lugar." },
4546
- "server-side-image-map": { description: "No se deben usar mapas de imagen del lado del servidor.", guidance: "Los mapas de imagen del lado del servidor (usando el atributo ismap) envían coordenadas de clic al servidor, lo cual es inaccesible para usuarios de teclado y lectores de pantalla que no pueden hacer clic con precisión en regiones específicas. Reemplace con mapas de imagen del lado del cliente (elementos <map> con <area>) que proporcionan acceso por teclado y nombres accesibles, o use imágenes/botones enlazados." },
4547
- label: { description: "Los elementos de formulario deben tener etiquetas. Use <label>, aria-label o aria-labelledby.", guidance: "Cada entrada de formulario necesita una etiqueta accesible para que los usuarios comprendan qué información ingresar. Use un elemento <label> con un atributo for que coincida con el id de la entrada, envuelva la entrada en un <label>, o use aria-label/aria-labelledby para componentes personalizados. Los placeholders no son suficientes como etiquetas ya que desaparecen al escribir." },
4548
- "form-field-multiple-labels": { description: "Los campos de formulario no deben tener múltiples elementos label.", guidance: "Cuando un campo de formulario tiene múltiples elementos <label> apuntando a él, las tecnologías de asistencia pueden anunciar solo una etiqueta o comportarse de manera inconsistente. Use un solo <label> y combine cualquier texto adicional en él, o use aria-describedby para información complementaria." },
4549
- "select-name": { description: "Los elementos select deben tener una etiqueta asociada programáticamente mediante <label>, aria-label o aria-labelledby.", guidance: "Los menús desplegables select necesitan etiquetas para que los usuarios comprendan qué elección están haciendo. Use un elemento <label> con un atributo for que coincida con el id del select, o envuelva el select en un <label>. Para selects sin etiquetas visibles, use aria-label. La primera <option> no es un sustituto de una etiqueta adecuada." },
4550
- "input-button-name": { description: "Los botones de entrada deben tener texto discernible mediante value, aria-label o aria-labelledby.", guidance: "Los botones de entrada (<input type='submit'>, type='button', type='reset'>) necesitan nombres accesibles para que los usuarios sepan qué acción realiza el botón. Agregue un atributo value con texto descriptivo (por ejemplo, value='Enviar formulario'), o use aria-label si el valor debe diferir del nombre accesible." },
4551
- "autocomplete-valid": { description: "El atributo autocomplete debe usar valores válidos de la especificación HTML.", guidance: "El atributo autocomplete ayuda a los usuarios a completar formularios identificando los propósitos de las entradas. Use valores estándar como 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. Esto beneficia a usuarios con discapacidades cognitivas, impedimentos motores y cualquier persona que use administradores de contraseñas o autocompletado. Consulte la especificación HTML para la lista completa de tokens válidos." },
4552
- "label-content-name-mismatch": { description: "Los elementos interactivos con texto visible deben tener nombres accesibles que contengan ese texto.", guidance: "Para los usuarios de control por voz que activan controles hablando su etiqueta visible, el nombre accesible debe incluir el texto visible. Si aria-label es 'Enviar formulario' pero el botón muestra 'Enviar', los usuarios de voz que digan 'clic Enviar' no lo activarán. Asegúrese de que aria-label/aria-labelledby contenga o coincida con el texto visible." },
4553
- "label-title-only": { description: "Los elementos de formulario no deben usar el atributo title como único nombre accesible.", guidance: "El atributo title no es confiable como etiqueta porque solo aparece al pasar el cursor/enfocar (no visible para usuarios táctiles) y a menudo es ignorado por las tecnologías de asistencia. Use un elemento <label> visible, aria-label o aria-labelledby en su lugar. El title puede complementar una etiqueta pero no debe reemplazarla." },
4554
- tabindex: { description: "Los elementos no deben tener tabindex mayor que 0, lo cual altera el orden natural de tabulación.", guidance: "Los valores positivos de tabindex fuerzan a los elementos al frente del orden de tabulación independientemente de la posición en el DOM, creando una navegación impredecible para los usuarios de teclado. Use tabindex='0' para agregar elementos al orden natural de tabulación, o tabindex='-1' para hacer elementos enfocables programáticamente pero no en el orden de tabulación. Confíe en el orden del DOM para la secuencia de tabulación." },
4555
- "focus-order-semantics": { description: "Los elementos que reciben el foco del teclado deben tener un rol apropiado para que las tecnologías de asistencia puedan transmitir su propósito. Los elementos no interactivos con tabindex='0' necesitan un rol ARIA interactivo válido.", guidance: "Al agregar tabindex='0' a elementos no interactivos como <div> o <span>, los lectores de pantalla los anuncian genéricamente. Agregue un rol apropiado (button, link, tab, etc.) para que los usuarios comprendan el propósito del elemento. También agregue manejadores de eventos de teclado (Enter/Espacio para botones, Enter para enlaces). Considere usar elementos interactivos nativos en su lugar." },
4556
- "nested-interactive": { description: "Los controles interactivos no deben estar anidados dentro de otros.", guidance: "Anidar elementos interactivos (como un botón dentro de un enlace, o un enlace dentro de un botón) crea un comportamiento impredecible y confunde a las tecnologías de asistencia. El navegador puede eliminar el elemento interno del árbol de accesibilidad. Reestructure el HTML para que los elementos interactivos sean hermanos, no anidados. Si necesita una tarjeta clicable, use CSS y JavaScript en lugar de anidar." },
4557
- "scrollable-region-focusable": { description: "Las regiones desplazables deben ser accesibles por teclado.", guidance: "El contenido que se desplaza debe ser accesible para los usuarios de teclado. Si una región tiene overflow:scroll u overflow:auto y contiene contenido desplazable, necesita tabindex='0' para ser enfocable, o debe contener elementos enfocables. Sin esto, los usuarios de teclado no pueden desplazar el contenido." },
4558
- accesskeys: { description: "Los valores del atributo accesskey deben ser únicos.", guidance: "Cuando múltiples elementos comparten la misma accesskey, el comportamiento del navegador se vuelve impredecible; generalmente solo se activa el primer elemento. Asegúrese de que cada valor de accesskey sea único dentro de la página. También considere que las accesskeys pueden entrar en conflicto con los atajos del navegador y del lector de pantalla, así que úselas con moderación." },
4559
- "heading-order": { description: "Los niveles de encabezado deben incrementarse de uno en uno; saltarse niveles (por ejemplo, h2 a h4) dificulta la navegación.", guidance: "Los usuarios de lectores de pantalla navegan por encabezados para comprender la estructura de la página. Saltarse niveles (h2 a h4) sugiere contenido faltante y crea confusión. Comience con h1 para el título de la página, luego use h2 para secciones principales, h3 para subsecciones, etc. Puede volver a subir (h3 a h2) al comenzar una nueva sección." },
4560
- "empty-heading": { description: "Los encabezados deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por encabezados, por lo que los encabezados vacíos crean puntos de navegación confusos. Asegúrese de que todos los encabezados contengan texto visible o nombres accesibles. Si un encabezado se usa puramente para estilo visual, use CSS en lugar de elementos de encabezado." },
4561
- "p-as-heading": { description: "Los párrafos no deben estilizarse para parecer encabezados.", guidance: "Cuando los párrafos se estilizan con negrita y fuentes grandes para parecer encabezados, los usuarios de lectores de pantalla pierden la estructura semántica. Use elementos de encabezado apropiados (h1-h6) en lugar de párrafos estilizados. Si necesita un estilo específico, aplique CSS a los elementos de encabezado manteniendo la jerarquía adecuada de encabezados." },
4562
- "landmark-one-main": { description: "La página debe tener exactamente un landmark main.", guidance: "El landmark main contiene el contenido principal de la página. Los lectores de pantalla permiten a los usuarios saltar directamente al contenido principal. Use un solo elemento <main> (o role='main') para envolver el contenido central, excluyendo encabezados, pies de página y navegación." },
4563
- "landmark-no-duplicate-banner": { description: "La página no debe tener más de un landmark banner.", guidance: "El landmark banner (típicamente <header>) identifica contenido orientado al sitio como logotipos y búsqueda. Solo se permite un banner de nivel superior por página. Si necesita múltiples encabezados, anídelos dentro de elementos de sección (article, section, aside) donde se convierten en encabezados de alcance en lugar de banners de nivel de página." },
4564
- "landmark-no-duplicate-contentinfo": { description: "La página no debe tener más de un landmark contentinfo.", guidance: "El landmark contentinfo (típicamente <footer>) contiene información sobre la página como derechos de autor e información de contacto. Solo se permite un contentinfo de nivel superior por página. Anide pies de página adicionales dentro de elementos de sección para delimitar su alcance." },
4565
- "landmark-no-duplicate-main": { description: "La página no debe tener más de un landmark main.", guidance: "Solo debe existir un landmark main por página. El landmark main identifica el área de contenido principal. Si tiene múltiples secciones de contenido, use <section> con encabezados apropiados en lugar de múltiples elementos main." },
4566
- "landmark-banner-is-top-level": { description: "El landmark banner no debe estar anidado dentro de otro landmark.", guidance: "El landmark banner debe ser un landmark de nivel superior, no anidado dentro de article, aside, main, nav o section. Si un header está dentro de estos elementos, automáticamente se convierte en un encabezado genérico en lugar de un banner. Elimine el role='banner' explícito de los headers anidados o reestructure la página." },
4567
- "landmark-contentinfo-is-top-level": { description: "El landmark contentinfo no debe estar anidado dentro de otro landmark.", guidance: "El landmark contentinfo debe ser un landmark de nivel superior. Un footer dentro de article, aside, main, nav o section se convierte en un pie de página de alcance, no en un landmark contentinfo. Elimine el role='contentinfo' explícito de los footers anidados o mueva el footer fuera de los elementos de sección." },
4568
- "landmark-main-is-top-level": { description: "El landmark main no debe estar anidado dentro de otro landmark.", guidance: "El landmark main debe ser un landmark de nivel superior ya que representa el contenido principal de la página. No anide <main> o role='main' dentro de elementos article, aside, nav o section." },
4569
- "landmark-complementary-is-top-level": { description: "El landmark aside (complementary) debe ser de nivel superior o estar directamente dentro de main.", guidance: "El landmark complementary (aside) debe ser de nivel superior o un hijo directo de main. Anidar aside profundamente dentro de otros landmarks reduce su descubrimiento para los usuarios de lectores de pantalla que navegan por landmarks." },
4570
- "landmark-unique": { description: "Los landmarks deben tener etiquetas únicas cuando hay múltiples del mismo tipo.", guidance: "Cuando una página tiene múltiples landmarks del mismo tipo (por ejemplo, múltiples elementos nav), cada uno debe tener un nombre accesible único mediante aria-label o aria-labelledby. Esto ayuda a los usuarios de lectores de pantalla a distinguirlos (por ejemplo, 'Navegación principal' vs 'Navegación del pie de página')." },
4571
- region: { description: "Todo el contenido de la página debe estar contenido dentro de landmarks.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por landmarks. El contenido fuera de los landmarks es más difícil de encontrar y comprender. Envuelva todo el contenido visible en landmarks apropiados: <header>, <nav>, <main>, <aside>, <footer>, o <section> con una etiqueta. Los enlaces de salto pueden existir fuera de los landmarks." },
4572
- list: { description: "<ul> y <ol> solo deben contener <li>, <script> o <template> como hijos directos.", guidance: "Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, reestructure su CSS para estilizar los elementos <li> directamente." },
4573
- listitem: { description: "Los elementos <li> deben estar contenidos en un <ul>, <ol> o <menu>.", guidance: "Los elementos de lista (<li>) solo tienen significado semántico dentro de un contenedor de lista (<ul>, <ol> o <menu>). Fuera de estos contenedores, las tecnologías de asistencia no pueden transmitir la relación de lista. Envuelva los elementos <li> en el contenedor de lista apropiado." },
4574
- dlitem: { description: "Los elementos <dt> y <dd> deben estar contenidos en un <dl>.", guidance: "Los términos de definición (<dt>) y las definiciones (<dd>) solo tienen significado semántico dentro de una lista de definiciones (<dl>). Fuera de <dl>, se tratan como texto genérico. Envuelva los pares relacionados de <dt> y <dd> en un elemento <dl> para transmitir la relación término/definición a las tecnologías de asistencia." },
4575
- "definition-list": { description: "Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script> o <template>.", guidance: "Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o reestructure usando el marcado adecuado de lista de definiciones." },
4576
- "aria-roles": { description: "Los valores de rol ARIA deben ser válidos.", guidance: "Los valores de rol inválidos son ignorados por las tecnologías de asistencia, lo que significa que el elemento no tendrá la semántica prevista. Verifique la ortografía y use solo roles definidos en la especificación WAI-ARIA. Los roles comunes incluyen: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem." },
4577
- "aria-valid-attr": { description: "Los atributos ARIA deben ser válidos (correctamente escritos).", guidance: "Los atributos ARIA mal escritos son ignorados por las tecnologías de asistencia. Verifique la ortografía contra la especificación WAI-ARIA. Errores comunes: aria-labeledby (debe ser aria-labelledby), aria-role (debe ser role), aria-description (válido en ARIA 1.3+)." },
4578
- "aria-valid-attr-value": { description: "Los atributos ARIA deben tener valores válidos.", guidance: "Cada atributo ARIA acepta tipos de valores específicos. Los atributos booleanos (aria-hidden, aria-disabled) aceptan solo 'true' o 'false'. Los atributos triestado (aria-checked, aria-pressed) también aceptan 'mixed'. Los atributos de token (aria-live, aria-autocomplete) aceptan valores predefinidos. Los atributos de referencia de ID (aria-labelledby, aria-describedby) deben referenciar IDs de elementos existentes." },
4579
- "aria-required-attr": { description: "Los elementos con roles ARIA deben tener todos los atributos ARIA requeridos.", guidance: "Algunos roles ARIA requieren atributos específicos para funcionar correctamente. Por ejemplo, checkbox requiere aria-checked, slider requiere aria-valuenow, heading requiere aria-level. Sin estos atributos, las tecnologías de asistencia no pueden transmitir el estado o valor del elemento a los usuarios. Agregue el atributo requerido faltante con un valor apropiado." },
4580
- "aria-allowed-attr": { description: "Los atributos ARIA deben estar permitidos para el rol del elemento.", guidance: "Cada rol ARIA admite atributos específicos. Usar atributos no admitidos crea confusión para las tecnologías de asistencia. Consulte la especificación ARIA para saber qué atributos son válidos para cada rol, o elimine el atributo si no es necesario." },
4581
- "aria-allowed-role": { description: "El rol ARIA debe ser apropiado para el elemento.", guidance: "No todos los roles ARIA se pueden aplicar a todos los elementos HTML. Muchos elementos tienen roles implícitos (por ejemplo, <header> es implícitamente banner, <nav> es navigation, <main> es main). Agregar un rol explícito que coincida con el rol implícito es redundante. Agregar un rol conflictivo rompe la semántica. Elimine el atributo role o use un elemento diferente." },
4582
- "aria-required-children": { description: "Ciertos roles ARIA requieren que estén presentes roles hijos específicos.", guidance: "Algunos roles ARIA representan contenedores que deben contener roles hijos específicos para una semántica adecuada. Por ejemplo, una lista debe contener listitems, un menú debe contener menuitems. Agregue los elementos hijos requeridos con roles apropiados, o use elementos HTML nativos que proporcionen esta semántica implícitamente (por ejemplo, <ul> con <li>)." },
4583
- "aria-required-parent": { description: "Ciertos roles ARIA deben estar contenidos dentro de roles padre específicos.", guidance: "Algunos roles ARIA representan elementos que deben existir dentro de roles de contenedor específicos. Por ejemplo, un listitem debe estar dentro de una lista, un tab debe estar dentro de un tablist. Envuelva el elemento en el padre apropiado, o use elementos HTML nativos que proporcionen esta estructura (por ejemplo, <li> dentro de <ul>)." },
4584
- "aria-hidden-body": { description: "aria-hidden='true' no debe estar presente en el body del documento.", guidance: "Establecer aria-hidden='true' en el elemento body oculta todo el contenido de la página de las tecnologías de asistencia, haciendo la página completamente inaccesible para los usuarios de lectores de pantalla. Elimine aria-hidden del elemento body. Si necesita ocultar contenido temporalmente (por ejemplo, detrás de un modal), use aria-hidden en secciones específicas en su lugar." },
4585
- "aria-hidden-focus": { description: "Los elementos con aria-hidden='true' no deben contener elementos enfocables.", guidance: "Cuando aria-hidden='true' oculta un elemento de las tecnologías de asistencia pero el elemento contiene hijos enfocables, los usuarios de teclado pueden enfocar esos hijos pero los usuarios de lectores de pantalla no sabrán que existen. Elimine los elementos enfocables de la región oculta, agregue tabindex='-1' a ellos, o elimine aria-hidden." },
4586
- "aria-command-name": { description: "Los comandos ARIA deben tener un nombre accesible.", guidance: "Los roles de comando ARIA interactivos (button, link, menuitem) deben tener nombres accesibles para que los usuarios sepan qué acción realizan. Agregue contenido de texto visible, aria-label o aria-labelledby para proporcionar un nombre." },
4587
- "aria-input-field-name": { description: "Los campos de entrada ARIA deben tener un nombre accesible.", guidance: "Los widgets de entrada ARIA (combobox, listbox, searchbox, slider, spinbutton, textbox) deben tener nombres accesibles para que los usuarios comprendan qué datos ingresar. Agregue una etiqueta visible con aria-labelledby, o use aria-label si una etiqueta visible no es posible." },
4588
- "aria-toggle-field-name": { description: "Los campos de alternancia ARIA deben tener un nombre accesible.", guidance: "Los controles de alternancia ARIA (checkbox, switch, radio, menuitemcheckbox, menuitemradio) deben tener nombres accesibles para que los usuarios comprendan qué opción están seleccionando. Agregue contenido de texto visible, aria-label, o use aria-labelledby para referenciar una etiqueta visible." },
4589
- "aria-meter-name": { description: "Los elementos meter ARIA deben tener un nombre accesible.", guidance: "Los elementos meter muestran un valor dentro de un rango conocido (como uso de disco o fortaleza de contraseña). Deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué se está midiendo. Use aria-label o aria-labelledby para proporcionar contexto." },
4590
- "aria-progressbar-name": { description: "Los elementos progressbar ARIA deben tener un nombre accesible.", guidance: "Los indicadores de progreso deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué proceso se está rastreando. Use aria-label (por ejemplo, 'Progreso de carga de archivo') o aria-labelledby para referenciar un encabezado o etiqueta visible." },
4591
- "aria-dialog-name": { description: "Los diálogos ARIA deben tener un nombre accesible.", guidance: "Los elementos dialog y alertdialog deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan el propósito del diálogo cuando se abre. Use aria-label o aria-labelledby apuntando al encabezado del diálogo. Los elementos nativos <dialog> también deben tener un nombre accesible." },
4592
- "aria-tooltip-name": { description: "Los tooltips ARIA deben tener un nombre accesible.", guidance: "Los elementos tooltip deben tener nombres accesibles (generalmente su contenido de texto). El contenido del tooltip típicamente sirve como el nombre accesible. Asegúrese de que el tooltip contenga contenido de texto descriptivo o tenga aria-label." },
4593
- "aria-treeitem-name": { description: "Los elementos treeitem ARIA deben tener un nombre accesible.", guidance: "Los elementos de árbol deben tener nombres accesibles para que los usuarios de lectores de pantalla puedan comprender la estructura del árbol y navegarlo eficazmente. Proporcione contenido de texto, aria-label o aria-labelledby para cada treeitem." },
4594
- "aria-prohibited-attr": { description: "Los atributos ARIA no deben estar prohibidos para el rol del elemento.", guidance: "Algunos roles ARIA prohíben ciertos atributos. Por ejemplo, roles como 'none', 'presentation', 'generic' y roles de nivel de texto (code, emphasis, strong) prohíben aria-label y aria-labelledby porque el nombramiento no está soportado para estos roles. Elimine los atributos prohibidos o cambie el rol." },
4595
- "presentation-role-conflict": { description: "Los elementos con role='presentation' o role='none' no deben ser enfocables ni tener atributos ARIA globales.", guidance: "Cuando un elemento tiene role='presentation' o role='none', está marcado como decorativo y se elimina del árbol de accesibilidad. Sin embargo, si el elemento es enfocable o tiene ciertos atributos ARIA, el rol de presentación se ignora y el elemento permanece accesible. Esto crea confusión. Elimine el rol de presentación, o elimine la enfocabilidad/atributos ARIA." },
4596
- "button-name": { description: "Los botones deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla necesitan saber qué hace un botón. Agregue contenido de texto visible, aria-label o aria-labelledby. Para botones de icono, use aria-label describiendo la acción (por ejemplo, aria-label='Cerrar'). Si el botón contiene una imagen, asegúrese de que la imagen tenga texto alt describiendo la acción del botón." },
4597
- "summary-name": { description: "Los elementos <summary> deben tener un nombre accesible.", guidance: "El elemento <summary> proporciona la etiqueta visible para un widget de divulgación <details>. Debe tener contenido de texto descriptivo para que los usuarios de lectores de pantalla comprendan qué se revelará al expandirse. Agregue texto claro y conciso que indique qué contenido está en la sección de detalles." },
4598
- "link-name": { description: "Los enlaces deben tener texto discernible mediante contenido, aria-label o aria-labelledby.", guidance: "Los usuarios de lectores de pantalla necesitan saber a dónde lleva un enlace. Agregue contenido de texto descriptivo, aria-label, o use aria-labelledby. Para enlaces de imagen, asegúrese de que la imagen tenga texto alt describiendo el destino del enlace. Evite texto genérico como 'haga clic aquí' o 'leer más': el texto del enlace debe tener sentido fuera de contexto." },
4599
- "skip-link": { description: "Los enlaces de salto deben apuntar a un destino válido en la página.", guidance: "Los enlaces de salto permiten a los usuarios de teclado omitir la navegación repetitiva y saltar directamente al contenido principal. El enlace de salto debe ser el primer elemento enfocable en la página, enlazar al contenido principal (por ejemplo, href='#main'), y hacerse visible cuando se enfoca. Puede estar visualmente oculto hasta enfocarse usando CSS." },
4600
- "link-in-text-block": { description: "Los enlaces dentro de bloques de texto deben distinguirse por algo más que solo el color.", guidance: "Los usuarios que no pueden percibir diferencias de color necesitan otras señales visuales para identificar enlaces. Los enlaces en texto deben tener subrayados u otros indicadores no cromáticos. Si usa solo color, asegure un contraste de 3:1 con el texto circundante Y proporcione una indicación adicional al enfocar/pasar el cursor." },
4601
- "html-has-lang": { description: "El elemento <html> debe tener un atributo lang.", guidance: "Los lectores de pantalla usan el atributo lang para determinar qué reglas de idioma y pronunciación usar. Sin él, el contenido puede pronunciarse incorrectamente. Establezca lang en el idioma principal de la página (por ejemplo, lang='en' para inglés, lang='es' para español)." },
4602
- "html-lang-valid": { description: "El atributo lang en <html> debe tener un valor válido.", guidance: "El atributo lang debe usar una etiqueta de idioma BCP 47 válida. Use un código de idioma de 2 o 3 letras (por ejemplo, 'en', 'fr', 'zh'), opcionalmente seguido de un código de región (por ejemplo, 'en-US', 'pt-BR'). Las etiquetas inválidas impiden que los lectores de pantalla pronuncien correctamente el contenido." },
4603
- "valid-lang": { description: "El atributo lang debe tener un valor válido en todos los elementos.", guidance: "Cuando aparece contenido en un idioma diferente dentro de una página (por ejemplo, una cita en francés en un documento en inglés), envuélvalo con un atributo lang para asegurar la pronunciación correcta. El valor de lang debe ser una etiqueta BCP 47 válida. Códigos comunes: en, es, fr, de, zh, ja, pt, ar, ru." },
4604
- "html-xml-lang-mismatch": { description: "Los atributos lang y xml:lang en <html> deben coincidir.", guidance: "En documentos XHTML, si tanto lang como xml:lang están presentes, deben especificar el mismo idioma base. Los valores no coincidentes confunden a las tecnologías de asistencia. Elimine xml:lang (preferido para HTML5) o asegúrese de que ambos atributos tengan valores idénticos." },
4605
- "td-headers-attr": { description: "Todas las celdas en una tabla que usan el atributo headers deben referenciar IDs de encabezado válidos.", guidance: "El atributo headers en las celdas de tabla debe referenciar IDs de celdas de encabezado (th o td) dentro de la misma tabla. Esto crea asociaciones explícitas para lectores de pantalla. Verifique que todos los IDs referenciados existan y estén escritos correctamente. Para tablas simples, considere usar scope en elementos th en su lugar." },
4606
- "th-has-data-cells": { description: "Los encabezados de tabla deben estar asociados con celdas de datos.", guidance: "Una tabla con celdas de encabezado (th) pero sin celdas de datos (td) probablemente es un mal uso del marcado de tabla para diseño o tiene contenido faltante. Agregue celdas de datos que los encabezados describan, o use marcado apropiado que no sea de tabla si estos no son datos tabulares." },
4607
- "td-has-header": { description: "Las celdas de datos en tablas mayores de 3x3 deben tener encabezados asociados.", guidance: "En tablas complejas, los usuarios de lectores de pantalla necesitan asociaciones de encabezados para comprender las celdas de datos. Use elementos th con atributo scope, o el atributo headers en elementos td. Para tablas simples (3x3 o menos), esto es menos crítico ya que el contexto generalmente es claro." },
4608
- "scope-attr-valid": { description: "El atributo scope en los encabezados de tabla debe tener un valor válido.", guidance: "El atributo scope indica a los lectores de pantalla a qué celdas se aplica un encabezado. Los valores válidos son: row, col, rowgroup, colgroup. Usar valores inválidos rompe la asociación entre encabezados y celdas." },
4609
- "empty-table-header": { description: "Las celdas de encabezado de tabla deben tener texto visible.", guidance: "Los encabezados de tabla vacíos no proporcionan información a los usuarios de lectores de pantalla. Agregue texto descriptivo al encabezado, o si el encabezado está intencionalmente vacío (como una celda de esquina), considere usar un elemento td en su lugar o agregar una etiqueta visualmente oculta." },
4610
- "duplicate-id-aria": { description: "Los IDs usados en asociaciones ARIA y label deben ser únicos para evitar referencias rotas.", guidance: "Cuando aria-labelledby, aria-describedby, aria-controls o label[for] referencian un ID duplicado, solo se usa el primer elemento coincidente. Esto rompe la relación prevista y puede dejar controles sin nombre o descripciones faltantes. Asegúrese de que los IDs referenciados por atributos ARIA y asociaciones de etiquetas sean únicos en todo el documento." },
4611
- "video-caption": { description: "Los elementos de video deben tener subtítulos mediante <track kind='captions'>.", guidance: "Los subtítulos proporcionan alternativas de texto para el contenido de audio en videos, beneficiando a usuarios sordos y aquellos que no pueden escuchar el audio. Agregue un elemento <track> con kind='captions' apuntando a un archivo de subtítulos WebVTT. Los subtítulos deben incluir tanto el diálogo como los efectos de sonido importantes." },
4612
- "audio-caption": { description: "Los elementos de audio deben tener una alternativa de texto o transcripción.", guidance: "El contenido solo de audio como podcasts o grabaciones necesita una alternativa de texto para usuarios sordos. Proporcione una transcripción en la misma página o enlazada cerca. La transcripción debe incluir todo el contenido hablado y descripciones de sonidos relevantes." },
4613
- "color-contrast": { description: "Los elementos de texto deben tener suficiente contraste de color contra el fondo.", guidance: "WCAG SC 1.4.3 requiere una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (>=24px o >=18.66px en negrita). Aumente el contraste oscureciendo el texto o aclarando el fondo, o viceversa." }
4488
+ const Xn = {
4489
+ "document-title": { description: "Documents must have a <title> element to provide users with an overview of content.", guidance: "Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').", messages: { "Document <title> element is empty.": "Document <title> element is empty.", "Document is missing a <title> element.": "Document is missing a <title> element." } },
4490
+ bypass: { description: "Page must have a mechanism to bypass repeated blocks of content.", guidance: 'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.', messages: { "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.": "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link." } },
4491
+ "page-has-heading-one": { description: "Page should contain a level-one heading.", guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title.", messages: { "Page does not contain a level-one heading.": "Page does not contain a level-one heading." } },
4492
+ "frame-title": { description: "Frames must have an accessible name.", guidance: "Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'.", messages: { "Frame is missing an accessible name. Add a title attribute.": "Frame is missing an accessible name. Add a title attribute." } },
4493
+ "frame-title-unique": { description: "Frame titles should be unique.", guidance: "When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content.", messages: { "Frame title is not unique. Use a distinct title for each frame.": "Frame title is not unique. Use a distinct title for each frame." } },
4494
+ "meta-viewport": { description: "Viewport meta tag must not disable user scaling.", guidance: "Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom.", messages: { "Viewport disables user scaling (user-scalable={0}). Remove this restriction.": "Viewport disables user scaling (user-scalable={0}). Remove this restriction.", "Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.": "Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove." } },
4495
+ "meta-refresh": { description: "Meta refresh must not redirect or refresh automatically.", guidance: "Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls.", messages: { "Page redirects after {0} seconds without warning. Use server-side redirect.": "Page redirects after {0} seconds without warning. Use server-side redirect.", "Page auto-refreshes after {0} seconds. Provide user control over refresh.": "Page auto-refreshes after {0} seconds. Provide user control over refresh." } },
4496
+ blink: { description: "The <blink> element must not be used.", guidance: "Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.", messages: { "The <blink> element causes accessibility issues. Remove it entirely.": "The <blink> element causes accessibility issues. Remove it entirely." } },
4497
+ marquee: { description: "The <marquee> element must not be used.", guidance: "Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.", messages: { "The <marquee> element causes accessibility issues. Replace with static content.": "The <marquee> element causes accessibility issues. Replace with static content." } },
4498
+ "img-alt": { description: `Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`, guidance: "Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead.", messages: { 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.': 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.', "Image element missing alt attribute.": "Image element missing alt attribute.", 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.': 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.' } },
4499
+ "svg-img-alt": { description: "SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.", guidance: "Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.", messages: { "{0} with role='{1}' has no accessible name.": "{0} with role='{1}' has no accessible name." } },
4500
+ "input-image-alt": { description: 'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.', guidance: "Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.", messages: { "Image input missing alt text.": "Image input missing alt text." } },
4501
+ "image-redundant-alt": { description: "Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.", guidance: "When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.", messages: { 'Alt text "{0}" duplicates surrounding {1} text.': 'Alt text "{0}" duplicates surrounding {1} text.' } },
4502
+ "image-alt-redundant-words": { description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.", guidance: "Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'Alt text "{0}" contains redundant word(s): {1}.' } },
4503
+ "area-alt": { description: "Image map <area> elements must have alternative text.", guidance: "Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.", messages: { "Image map <area> element is missing alternative text.": "Image map <area> element is missing alternative text." } },
4504
+ "object-alt": { description: "<object> elements must have alternative text.", guidance: "Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.", messages: { "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.": "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute." } },
4505
+ "role-img-alt": { description: "Elements with role='img' must have an accessible name.", guidance: "When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.", messages: { "Element with role='img' has no accessible name. Add aria-label or aria-labelledby.": "Element with role='img' has no accessible name. Add aria-label or aria-labelledby." } },
4506
+ "server-side-image-map": { description: "Server-side image maps must not be used.", guidance: "Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.", messages: { "Server-side image map detected. Use client-side image map with <map> and <area> elements instead.": "Server-side image map detected. Use client-side image map with <map> and <area> elements instead." } },
4507
+ label: { description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.", guidance: "Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing.", messages: { "Form element has no accessible label.": "Form element has no accessible label." } },
4508
+ "form-field-multiple-labels": { description: "Form fields should not have multiple label elements.", guidance: "When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information.", messages: { "Form field has {0} labels. Use a single label element.": "Form field has {0} labels. Use a single label element." } },
4509
+ "select-name": { description: "Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.", guidance: "Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label.", messages: { "Select element has no accessible name.": "Select element has no accessible name." } },
4510
+ "input-button-name": { description: "Input buttons must have discernible text via value, aria-label, or aria-labelledby.", guidance: "Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name.", messages: { "Input button has no discernible text.": "Input button has no discernible text." } },
4511
+ "autocomplete-valid": { description: "Autocomplete attribute must use valid values from the HTML specification.", guidance: "The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens.", messages: { 'Invalid autocomplete value "{0}".': 'Invalid autocomplete value "{0}".' } },
4512
+ "label-content-name-mismatch": { description: "Interactive elements with visible text must have accessible names that contain that text.", guidance: "For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text.", messages: { 'Accessible name "{0}" does not contain visible text "{1}".': 'Accessible name "{0}" does not contain visible text "{1}".' } },
4513
+ "label-title-only": { description: "Form elements should not use title attribute as the only accessible name.", guidance: "The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.", messages: { "Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.": "Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead." } },
4514
+ tabindex: { description: "Elements should not have tabindex greater than 0, which disrupts natural tab order.", guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.", messages: { 'Element has tabindex="{0}" which disrupts tab order.': 'Element has tabindex="{0}" which disrupts tab order.' } },
4515
+ "focus-order-semantics": { description: "Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.", guidance: "When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead.", messages: { 'Non-interactive <{0}> with tabindex="0" has no interactive role.': 'Non-interactive <{0}> with tabindex="0" has no interactive role.' } },
4516
+ "nested-interactive": { description: "Interactive controls must not be nested inside each other.", guidance: "Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting.", messages: { "Interactive element <{0}> is nested inside <{1}>.": "Interactive element <{0}> is nested inside <{1}>." } },
4517
+ "scrollable-region-focusable": { description: "Scrollable regions must be keyboard accessible.", guidance: "Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content.", messages: { "Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.": "Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements." } },
4518
+ accesskeys: { description: "Accesskey attribute values must be unique.", guidance: "When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly.", messages: { 'Duplicate accesskey "{0}". Each accesskey must be unique.': 'Duplicate accesskey "{0}". Each accesskey must be unique.' } },
4519
+ "heading-order": { description: "Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.", guidance: "Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section.", messages: { "Heading level {0} skipped from level {1}.": "Heading level {0} skipped from level {1}." } },
4520
+ "empty-heading": { description: "Headings must have discernible text.", guidance: "Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements.", messages: { "Heading is empty. Add text content or remove the heading element.": "Heading is empty. Add text content or remove the heading element." } },
4521
+ "p-as-heading": { description: "Paragraphs should not be styled to look like headings.", guidance: "When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy.", messages: { "Paragraph appears to be styled as a heading. Use an h1-h6 element instead.": "Paragraph appears to be styled as a heading. Use an h1-h6 element instead." } },
4522
+ "landmark-one-main": { description: "Page should have exactly one main landmark.", guidance: "The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation.", messages: { "Page has no main landmark.": "Page has no main landmark.", "Page has multiple main landmarks.": "Page has multiple main landmarks." } },
4523
+ "landmark-no-duplicate-banner": { description: "Page should not have more than one banner landmark.", guidance: "The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners.", messages: { "Page has multiple banner landmarks.": "Page has multiple banner landmarks." } },
4524
+ "landmark-no-duplicate-contentinfo": { description: "Page should not have more than one contentinfo landmark.", guidance: "The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them.", messages: { "Page has multiple contentinfo landmarks.": "Page has multiple contentinfo landmarks." } },
4525
+ "landmark-no-duplicate-main": { description: "Page should not have more than one main landmark.", guidance: "Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements.", messages: { "Page has multiple main landmarks.": "Page has multiple main landmarks." } },
4526
+ "landmark-banner-is-top-level": { description: "Banner landmark should not be nested within another landmark.", guidance: "The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page.", messages: { "Banner landmark is nested within another landmark.": "Banner landmark is nested within another landmark." } },
4527
+ "landmark-contentinfo-is-top-level": { description: "Contentinfo landmark should not be nested within another landmark.", guidance: "The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements.", messages: { "Contentinfo landmark is nested within another landmark.": "Contentinfo landmark is nested within another landmark." } },
4528
+ "landmark-main-is-top-level": { description: "Main landmark should not be nested within another landmark.", guidance: "The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements.", messages: { "Main landmark is nested within another landmark.": "Main landmark is nested within another landmark." } },
4529
+ "landmark-complementary-is-top-level": { description: "Aside (complementary) landmark should be top-level or directly inside main.", guidance: "The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks.", messages: { "Complementary landmark should be top-level.": "Complementary landmark should be top-level." } },
4530
+ "landmark-unique": { description: "Landmarks should have unique labels when there are multiple of the same type.", guidance: "When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation').", messages: { 'Multiple {0} landmarks have the same label "{1}".': 'Multiple {0} landmarks have the same label "{1}".', "Multiple {0} landmarks have no label. Add unique aria-label attributes.": "Multiple {0} landmarks have no label. Add unique aria-label attributes." } },
4531
+ region: { description: "All page content should be contained within landmarks.", guidance: "Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks.", messages: { "Content is not contained within a landmark region.": "Content is not contained within a landmark region." } },
4532
+ list: { description: "<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.", guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly.", messages: { "List contains non-<li> child <{0}>.": "List contains non-<li> child <{0}>." } },
4533
+ listitem: { description: "<li> elements must be contained in a <ul>, <ol>, or <menu>.", guidance: "List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container.", messages: { "<li> is not contained in a <ul>, <ol>, or <menu>.": "<li> is not contained in a <ul>, <ol>, or <menu>." } },
4534
+ dlitem: { description: "<dt> and <dd> elements must be contained in a <dl>.", guidance: "Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.", messages: { "<{0}> is not contained in a <dl>.": "<{0}> is not contained in a <dl>." } },
4535
+ "definition-list": { description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.", guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or restructure using proper definition list markup.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contains invalid child <{0}>." } },
4536
+ "aria-roles": { description: "ARIA role values must be valid.", guidance: "Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.", messages: { 'Invalid ARIA role "{0}".': 'Invalid ARIA role "{0}".' } },
4537
+ "aria-valid-attr": { description: "ARIA attributes must be valid (correctly spelled).", guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).", messages: { 'Invalid ARIA attribute "{0}".': 'Invalid ARIA attribute "{0}".' } },
4538
+ "aria-valid-attr-value": { description: "ARIA attributes must have valid values.", guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.", messages: { '{0} must be "true" or "false", got "{1}".': '{0} must be "true" or "false", got "{1}".', '{0} must be "true", "false", or "mixed", got "{1}".': '{0} must be "true", "false", or "mixed", got "{1}".', '{0} must be an integer, got "{1}".': '{0} must be an integer, got "{1}".', '{0} must be a number, got "{1}".': '{0} must be a number, got "{1}".', 'Invalid value "{0}" for {1}.': 'Invalid value "{0}" for {1}.' } },
4539
+ "aria-required-attr": { description: "Elements with ARIA roles must have all required ARIA attributes.", guidance: "Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value.", messages: { 'Role "{0}" requires attribute "{1}".': 'Role "{0}" requires attribute "{1}".' } },
4540
+ "aria-allowed-attr": { description: "ARIA attributes must be allowed for the element's role.", guidance: "Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.", messages: { 'ARIA attribute "{0}" is not allowed on role "{1}".': 'ARIA attribute "{0}" is not allowed on role "{1}".' } },
4541
+ "aria-allowed-role": { description: "ARIA role must be appropriate for the element.", guidance: "Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element.", messages: { "Element <{0}> should not have an explicit role.": "Element <{0}> should not have an explicit role.", 'Role "{0}" is not allowed on element <{1}>.': 'Role "{0}" is not allowed on element <{1}>.' } },
4542
+ "aria-required-children": { description: "Certain ARIA roles require specific child roles to be present.", guidance: "Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>).", messages: { 'Role "{0}" requires children with role: {1}.': 'Role "{0}" requires children with role: {1}.' } },
4543
+ "aria-required-parent": { description: "Certain ARIA roles must be contained within specific parent roles.", guidance: "Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>).", messages: { 'Role "{0}" must be contained within: {1}.': 'Role "{0}" must be contained within: {1}.' } },
4544
+ "aria-hidden-body": { description: "aria-hidden='true' must not be present on the document body.", guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.", messages: { "aria-hidden='true' on body hides all content from assistive technologies.": "aria-hidden='true' on body hides all content from assistive technologies." } },
4545
+ "aria-hidden-focus": { description: "Elements with aria-hidden='true' must not contain focusable elements.", guidance: "When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.", messages: { "Focusable element is inside an aria-hidden region.": "Focusable element is inside an aria-hidden region." } },
4546
+ "aria-command-name": { description: "ARIA commands must have an accessible name.", guidance: "Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name.", messages: { "ARIA command has no accessible name.": "ARIA command has no accessible name." } },
4547
+ "aria-input-field-name": { description: "ARIA input fields must have an accessible name.", guidance: "ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible.", messages: { "ARIA input field has no accessible name.": "ARIA input field has no accessible name." } },
4548
+ "aria-toggle-field-name": { description: "ARIA toggle fields must have an accessible name.", guidance: "ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label.", messages: { "ARIA toggle field has no accessible name.": "ARIA toggle field has no accessible name." } },
4549
+ "aria-meter-name": { description: "ARIA meter elements must have an accessible name.", guidance: "Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context.", messages: { "Meter has no accessible name.": "Meter has no accessible name." } },
4550
+ "aria-progressbar-name": { description: "ARIA progressbar elements must have an accessible name.", guidance: "Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label.", messages: { "Progressbar has no accessible name.": "Progressbar has no accessible name." } },
4551
+ "aria-dialog-name": { description: "ARIA dialogs must have an accessible name.", guidance: "Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name.", messages: { "Dialog has no accessible name.": "Dialog has no accessible name." } },
4552
+ "aria-tooltip-name": { description: "ARIA tooltips must have an accessible name.", guidance: "Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label.", messages: { "Tooltip has no accessible name.": "Tooltip has no accessible name." } },
4553
+ "aria-treeitem-name": { description: "ARIA treeitem elements must have an accessible name.", guidance: "Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem.", messages: { "Treeitem has no accessible name.": "Treeitem has no accessible name." } },
4554
+ "aria-prohibited-attr": { description: "ARIA attributes must not be prohibited for the element's role.", guidance: "Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.", messages: { "aria-label and aria-labelledby are prohibited on <{0}> elements.": "aria-label and aria-labelledby are prohibited on <{0}> elements.", 'aria-label and aria-labelledby are prohibited on role "{0}".': 'aria-label and aria-labelledby are prohibited on role "{0}".', 'Attribute "{0}" is prohibited on role "{1}".': 'Attribute "{0}" is prohibited on role "{1}".' } },
4555
+ "presentation-role-conflict": { description: "Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.", guidance: "When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes.", messages: { "Presentation role conflicts with: {0}. The role will be ignored.": "Presentation role conflicts with: {0}. The role will be ignored.", 'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.': 'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.' } },
4556
+ "button-name": { description: "Buttons must have discernible text.", guidance: "Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action.", messages: { "Button has no discernible text.": "Button has no discernible text." } },
4557
+ "summary-name": { description: "<summary> elements must have an accessible name.", guidance: "The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section.", messages: { "<summary> element has no accessible name. Add descriptive text.": "<summary> element has no accessible name. Add descriptive text." } },
4558
+ "link-name": { description: "Links must have discernible text via content, aria-label, or aria-labelledby.", guidance: "Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context.", messages: { "Link has no discernible text.": "Link has no discernible text." } },
4559
+ "skip-link": { description: "Skip links must point to a valid target on the page.", guidance: "Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS.", messages: { 'Skip link points to "#{0}" which does not exist on the page.': 'Skip link points to "#{0}" which does not exist on the page.' } },
4560
+ "link-in-text-block": { description: "Links within text blocks must be distinguishable by more than color alone.", guidance: "Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover.", messages: { "Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.": "Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border." } },
4561
+ "html-has-lang": { description: "The <html> element must have a lang attribute.", guidance: "Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page (e.g., lang='en' for English, lang='es' for Spanish).", messages: { "<html> element missing lang attribute.": "<html> element missing lang attribute." } },
4562
+ "html-lang-valid": { description: "The lang attribute on <html> must have a valid value.", guidance: "The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content.", messages: { 'Invalid lang attribute value "{0}".': 'Invalid lang attribute value "{0}".' } },
4563
+ "valid-lang": { description: "The lang attribute must have a valid value on all elements.", guidance: "When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru.", messages: { "Empty lang attribute value.": "Empty lang attribute value.", 'Invalid lang attribute value "{0}".': 'Invalid lang attribute value "{0}".' } },
4564
+ "html-xml-lang-mismatch": { description: "The lang and xml:lang attributes on <html> must match.", guidance: "In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.", messages: { 'lang="{0}" and xml:lang="{1}" do not match.': 'lang="{0}" and xml:lang="{1}" do not match.' } },
4565
+ "td-headers-attr": { description: "All cells in a table using headers attribute must reference valid header IDs.", guidance: "The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead.", messages: { 'Headers attribute references the cell itself ("{0}").': 'Headers attribute references the cell itself ("{0}").', 'Headers attribute references non-existent ID "{0}".': 'Headers attribute references non-existent ID "{0}".' } },
4566
+ "th-has-data-cells": { description: "Table headers should be associated with data cells.", guidance: "A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data.", messages: { "Table has header cells but no data cells.": "Table has header cells but no data cells." } },
4567
+ "td-has-header": { description: "Data cells in tables larger than 3x3 should have associated headers.", guidance: "In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear.", messages: { "Data cell has no associated header. Add th elements with scope, or headers attribute.": "Data cell has no associated header. Add th elements with scope, or headers attribute." } },
4568
+ "scope-attr-valid": { description: "The scope attribute on table headers must have a valid value.", guidance: "The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells.", messages: { 'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.': 'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.' } },
4569
+ "empty-table-header": { description: "Table header cells should have visible text.", guidance: "Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label.", messages: { "Table header cell is empty. Add text or use aria-label.": "Table header cell is empty. Add text or use aria-label." } },
4570
+ "duplicate-id-aria": { description: "IDs used in ARIA and label associations must be unique to avoid broken references.", guidance: "When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document.", messages: { 'Duplicate ID "{0}" referenced by {1}.': 'Duplicate ID "{0}" referenced by {1}.' } },
4571
+ "video-caption": { description: "Video elements must have captions via <track kind='captions'>.", guidance: "Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects.", messages: { "Video element has no captions track.": "Video element has no captions track." } },
4572
+ "audio-caption": { description: "Audio elements should have a text alternative or transcript.", guidance: "Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds.", messages: { "Audio element has no transcript or text alternative. Add a transcript or track element.": "Audio element has no transcript or text alternative. Add a transcript or track element." } },
4573
+ "color-contrast": { description: "Text elements must have sufficient color contrast against the background.", guidance: "WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa.", messages: { "Insufficient color contrast ratio of {0}:1 (required {1}:1).": "Insufficient color contrast ratio of {0}:1 (required {1}:1)." } }
4574
+ }, Kn = {
4575
+ "document-title": { description: "Los documentos deben tener un elemento <title> para proporcionar a los usuarios una vista general del contenido.", guidance: "Los usuarios de lectores de pantalla dependen de los títulos de página para identificar y navegar entre pestañas/ventanas. Agregue un elemento <title> descriptivo en <head> que resuma el propósito de la página. Mantenga los títulos únicos en todo el sitio, colocando el contenido específico antes del nombre del sitio (por ejemplo, 'Contáctenos - Acme Corp').", messages: { "Document <title> element is empty.": "El elemento <title> del documento está vacío.", "Document is missing a <title> element.": "Al documento le falta un elemento <title>." } },
4576
+ bypass: { description: "La página debe tener un mecanismo para omitir bloques de contenido repetidos.", guidance: 'Los usuarios de teclado deben poder omitir contenido repetitivo como la navegación. Proporcione un enlace de salto en la parte superior de la página que enlace al contenido principal (por ejemplo, <a href="#main">Saltar al contenido principal</a>), o use un landmark <main>. Los lectores de pantalla pueden saltar directamente a los landmarks, por lo que un elemento <main> correctamente marcado satisface este requisito.', messages: { "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.": "La página no tiene un mecanismo para omitir contenido repetido. Agregue un landmark <main> o un enlace de salto." } },
4577
+ "page-has-heading-one": { description: "La página debe contener un encabezado de nivel uno.", guidance: "Un encabezado de nivel uno (<h1> o role='heading' con aria-level='1') ayuda a los usuarios a comprender el tema de la página y proporciona un punto de referencia para la navegación con lector de pantalla. Cada página debe tener exactamente un h1 que describa el contenido principal, típicamente coincidiendo o similar al título de la página.", messages: { "Page does not contain a level-one heading.": "La página no contiene un encabezado de nivel uno." } },
4578
+ "frame-title": { description: "Los marcos deben tener un nombre accesible.", guidance: "Los lectores de pantalla anuncian los títulos de los marcos cuando los usuarios navegan por ellos. Agregue un atributo title a los elementos <iframe> y <frame> que describa el propósito del marco (por ejemplo, <iframe title='Reproductor de video'>). Evite títulos genéricos como 'marco' o 'iframe'. Si el marco es decorativo, use aria-hidden='true'.", messages: { "Frame is missing an accessible name. Add a title attribute.": "Al marco le falta un nombre accesible. Agregue un atributo title." } },
4579
+ "frame-title-unique": { description: "Los títulos de los marcos deben ser únicos.", guidance: "Cuando varios marcos tienen títulos idénticos, los usuarios de lectores de pantalla no pueden distinguirlos. Dé a cada marco un título único y descriptivo que explique su propósito específico o contenido.", messages: { "Frame title is not unique. Use a distinct title for each frame.": "El título del marco no es único. Use un título distinto para cada marco." } },
4580
+ "meta-viewport": { description: "La etiqueta meta viewport no debe deshabilitar el zoom del usuario.", guidance: "Los usuarios con baja visión necesitan ampliar el contenido al 200% o más. Establecer user-scalable=no o maximum-scale=1 impide el zoom y no cumple con WCAG. Elimine estas restricciones. Si su diseño se rompe con zoom alto, corrija el diseño responsivo en lugar de impedir el zoom.", messages: { "Viewport disables user scaling (user-scalable={0}). Remove this restriction.": "El viewport deshabilita el escalado del usuario (user-scalable={0}). Elimine esta restricción.", "Viewport maximum-scale={0} restricts zooming. Set to at least 2 or remove.": "El viewport maximum-scale={0} restringe el zoom. Establezca al menos 2 o elimínelo." } },
4581
+ "meta-refresh": { description: "La etiqueta meta refresh no debe redirigir o actualizar automáticamente.", guidance: "Las actualizaciones o redirecciones automáticas de página pueden desorientar a los usuarios, especialmente aquellos que usan lectores de pantalla o con discapacidades cognitivas. Pueden perder su lugar o no tener tiempo para leer el contenido. Si se necesita una redirección, use una redirección del lado del servidor (HTTP 301/302). Para actualizaciones temporizadas, proporcione controles al usuario.", messages: { "Page redirects after {0} seconds without warning. Use server-side redirect.": "La página redirige después de {0} segundos sin advertencia. Use redirección del lado del servidor.", "Page auto-refreshes after {0} seconds. Provide user control over refresh.": "La página se actualiza automáticamente después de {0} segundos. Proporcione control al usuario sobre la actualización." } },
4582
+ blink: { description: "El elemento <blink> no debe usarse.", guidance: "El contenido parpadeante puede causar convulsiones en usuarios con epilepsia fotosensible y es una distracción para usuarios con trastornos de atención. El elemento <blink> está obsoleto y nunca debe usarse. Si necesita llamar la atención sobre el contenido, use métodos menos intrusivos como color, bordes o iconos.", messages: { "The <blink> element causes accessibility issues. Remove it entirely.": "El elemento <blink> causa problemas de accesibilidad. Elimínelo por completo." } },
4583
+ marquee: { description: "El elemento <marquee> no debe usarse.", guidance: "El contenido que se desplaza o se mueve es difícil de leer para muchos usuarios, especialmente aquellos con discapacidades cognitivas o visuales. El elemento <marquee> está obsoleto. Reemplace el texto en movimiento con contenido estático. Si el contenido debe desplazarse, proporcione controles de pausa/detención y asegúrese de que se detenga después de 5 segundos.", messages: { "The <marquee> element causes accessibility issues. Replace with static content.": "El elemento <marquee> causa problemas de accesibilidad. Reemplace con contenido estático." } },
4584
+ "img-alt": { description: `Las imágenes deben tener texto alternativo. Agregue un atributo alt a los elementos <img>. Las imágenes decorativas pueden usar un atributo alt vacío (alt=""), role='none' o role='presentation'.`, guidance: "Cada imagen necesita un atributo alt. Para imágenes informativas, describa el contenido o la función de manera concisa. Para imágenes decorativas (fondos, espaciadores, adornos puramente visuales), use alt='' para ocultarlas de los lectores de pantalla. Nunca omita alt por completo: los lectores de pantalla pueden leer el nombre del archivo en su lugar.", messages: { 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.': 'La imagen tiene texto alt solo con espacios en blanco. Use alt="" para imágenes decorativas o proporcione texto descriptivo.', "Image element missing alt attribute.": "Al elemento de imagen le falta el atributo alt.", 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.': 'El elemento con role="img" no tiene nombre accesible. Agregue aria-label o aria-labelledby.' } },
4585
+ "svg-img-alt": { description: "Los elementos SVG con rol img, graphics-document o graphics-symbol deben tener un nombre accesible mediante un elemento <title>, aria-label o aria-labelledby.", guidance: "Los SVG en línea con role='img' necesitan nombres accesibles. Agregue un elemento <title> como primer hijo del SVG (los lectores de pantalla lo anunciarán), o use aria-label en el elemento SVG. Para SVGs complejos, use aria-labelledby haciendo referencia tanto a un elemento <title> como a un elemento <desc>. Los SVGs decorativos deben usar aria-hidden='true' en su lugar.", messages: { "{0} with role='{1}' has no accessible name.": "{0} con role='{1}' no tiene nombre accesible." } },
4586
+ "input-image-alt": { description: 'Las entradas de imagen (<input type="image">) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.', guidance: "Los botones de imagen (<input type='image'>) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.", messages: { "Image input missing alt text.": "A la entrada de imagen le falta texto alt." } },
4587
+ "image-redundant-alt": { description: "El texto alternativo de la imagen no debe duplicar el texto del enlace o botón adyacente. Cuando el texto alt repite el texto circundante, los usuarios de lectores de pantalla escuchan la misma información dos veces.", guidance: "Cuando una imagen está dentro de un enlace o botón que también tiene texto, haga que el texto alt sea complementario en lugar de idéntico. Si la imagen es puramente decorativa en ese contexto, use alt='' para evitar la repetición.", messages: { 'Alt text "{0}" duplicates surrounding {1} text.': 'El texto alt "{0}" duplica el texto del {1} circundante.' } },
4588
+ "image-alt-redundant-words": { description: "El texto alternativo de la imagen no debe contener palabras como 'imagen', 'foto' o 'fotografía': los lectores de pantalla ya anuncian el tipo de elemento.", guidance: "Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'El texto alt "{0}" contiene palabra(s) redundante(s): {1}.' } },
4589
+ "area-alt": { description: "Los elementos <area> del mapa de imagen deben tener texto alternativo.", guidance: "Cada región clicable en un mapa de imagen necesita texto alternativo para que los usuarios de lectores de pantalla sepan qué representa la región. Agregue un atributo alt a cada elemento <area> describiendo su propósito. Para mapas de imagen complejos, considere usar enfoques alternativos como SVG con enlaces incrustados, o una lista de enlaces de texto.", messages: { "Image map <area> element is missing alternative text.": "Al elemento <area> del mapa de imagen le falta texto alternativo." } },
4590
+ "object-alt": { description: "Los elementos <object> deben tener texto alternativo.", guidance: "Los elementos object incrustan contenido externo que puede no ser accesible para todos los usuarios. Proporcione texto alternativo mediante aria-label, aria-labelledby o un atributo title. El contenido de respaldo dentro de <object> solo se muestra cuando el objeto no se carga y no sirve como nombre accesible.", messages: { "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.": "Al elemento <object> le falta texto alternativo. Agregue aria-label, aria-labelledby o un atributo title." } },
4591
+ "role-img-alt": { description: "Los elementos con role='img' deben tener un nombre accesible.", guidance: "Cuando asigna role='img' a un elemento (como un div que contiene fuentes de iconos o fondos CSS), debe proporcionar un nombre accesible mediante aria-label o aria-labelledby. Sin esto, los usuarios de lectores de pantalla no tienen forma de entender lo que representa la imagen. Si la imagen es decorativa, use role='presentation' o role='none' en su lugar.", messages: { "Element with role='img' has no accessible name. Add aria-label or aria-labelledby.": "El elemento con role='img' no tiene nombre accesible. Agregue aria-label o aria-labelledby." } },
4592
+ "server-side-image-map": { description: "No se deben usar mapas de imagen del lado del servidor.", guidance: "Los mapas de imagen del lado del servidor (usando el atributo ismap) envían coordenadas de clic al servidor, lo cual es inaccesible para usuarios de teclado y lectores de pantalla que no pueden hacer clic con precisión en regiones específicas. Reemplace con mapas de imagen del lado del cliente (elementos <map> con <area>) que proporcionan acceso por teclado y nombres accesibles, o use imágenes/botones enlazados.", messages: { "Server-side image map detected. Use client-side image map with <map> and <area> elements instead.": "Se detectó un mapa de imagen del lado del servidor. Use un mapa de imagen del lado del cliente con elementos <map> y <area> en su lugar." } },
4593
+ label: { description: "Los elementos de formulario deben tener etiquetas. Use <label>, aria-label o aria-labelledby.", guidance: "Cada entrada de formulario necesita una etiqueta accesible para que los usuarios comprendan qué información ingresar. Use un elemento <label> con un atributo for que coincida con el id de la entrada, envuelva la entrada en un <label>, o use aria-label/aria-labelledby para componentes personalizados. Los placeholders no son suficientes como etiquetas ya que desaparecen al escribir.", messages: { "Form element has no accessible label.": "El elemento de formulario no tiene etiqueta accesible." } },
4594
+ "form-field-multiple-labels": { description: "Los campos de formulario no deben tener múltiples elementos label.", guidance: "Cuando un campo de formulario tiene múltiples elementos <label> apuntando a él, las tecnologías de asistencia pueden anunciar solo una etiqueta o comportarse de manera inconsistente. Use un solo <label> y combine cualquier texto adicional en él, o use aria-describedby para información complementaria.", messages: { "Form field has {0} labels. Use a single label element.": "El campo de formulario tiene {0} etiquetas. Use un solo elemento label." } },
4595
+ "select-name": { description: "Los elementos select deben tener una etiqueta asociada programáticamente mediante <label>, aria-label o aria-labelledby.", guidance: "Los menús desplegables select necesitan etiquetas para que los usuarios comprendan qué elección están haciendo. Use un elemento <label> con un atributo for que coincida con el id del select, o envuelva el select en un <label>. Para selects sin etiquetas visibles, use aria-label. La primera <option> no es un sustituto de una etiqueta adecuada.", messages: { "Select element has no accessible name.": "El elemento select no tiene nombre accesible." } },
4596
+ "input-button-name": { description: "Los botones de entrada deben tener texto discernible mediante value, aria-label o aria-labelledby.", guidance: "Los botones de entrada (<input type='submit'>, type='button', type='reset'>) necesitan nombres accesibles para que los usuarios sepan qué acción realiza el botón. Agregue un atributo value con texto descriptivo (por ejemplo, value='Enviar formulario'), o use aria-label si el valor debe diferir del nombre accesible.", messages: { "Input button has no discernible text.": "El botón de entrada no tiene texto discernible." } },
4597
+ "autocomplete-valid": { description: "El atributo autocomplete debe usar valores válidos de la especificación HTML.", guidance: "El atributo autocomplete ayuda a los usuarios a completar formularios identificando los propósitos de las entradas. Use valores estándar como 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. Esto beneficia a usuarios con discapacidades cognitivas, impedimentos motores y cualquier persona que use administradores de contraseñas o autocompletado. Consulte la especificación HTML para la lista completa de tokens válidos.", messages: { 'Invalid autocomplete value "{0}".': 'Valor de autocomplete inválido "{0}".' } },
4598
+ "label-content-name-mismatch": { description: "Los elementos interactivos con texto visible deben tener nombres accesibles que contengan ese texto.", guidance: "Para los usuarios de control por voz que activan controles hablando su etiqueta visible, el nombre accesible debe incluir el texto visible. Si aria-label es 'Enviar formulario' pero el botón muestra 'Enviar', los usuarios de voz que digan 'clic Enviar' no lo activarán. Asegúrese de que aria-label/aria-labelledby contenga o coincida con el texto visible.", messages: { 'Accessible name "{0}" does not contain visible text "{1}".': 'El nombre accesible "{0}" no contiene el texto visible "{1}".' } },
4599
+ "label-title-only": { description: "Los elementos de formulario no deben usar el atributo title como único nombre accesible.", guidance: "El atributo title no es confiable como etiqueta porque solo aparece al pasar el cursor/enfocar (no visible para usuarios táctiles) y a menudo es ignorado por las tecnologías de asistencia. Use un elemento <label> visible, aria-label o aria-labelledby en su lugar. El title puede complementar una etiqueta pero no debe reemplazarla.", messages: { "Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead.": "El elemento de formulario usa el atributo title como única etiqueta. Use <label>, aria-label o aria-labelledby en su lugar." } },
4600
+ tabindex: { description: "Los elementos no deben tener tabindex mayor que 0, lo cual altera el orden natural de tabulación.", guidance: "Los valores positivos de tabindex fuerzan a los elementos al frente del orden de tabulación independientemente de la posición en el DOM, creando una navegación impredecible para los usuarios de teclado. Use tabindex='0' para agregar elementos al orden natural de tabulación, o tabindex='-1' para hacer elementos enfocables programáticamente pero no en el orden de tabulación. Confíe en el orden del DOM para la secuencia de tabulación.", messages: { 'Element has tabindex="{0}" which disrupts tab order.': 'El elemento tiene tabindex="{0}" lo cual altera el orden de tabulación.' } },
4601
+ "focus-order-semantics": { description: "Los elementos que reciben el foco del teclado deben tener un rol apropiado para que las tecnologías de asistencia puedan transmitir su propósito. Los elementos no interactivos con tabindex='0' necesitan un rol ARIA interactivo válido.", guidance: "Al agregar tabindex='0' a elementos no interactivos como <div> o <span>, los lectores de pantalla los anuncian genéricamente. Agregue un rol apropiado (button, link, tab, etc.) para que los usuarios comprendan el propósito del elemento. También agregue manejadores de eventos de teclado (Enter/Espacio para botones, Enter para enlaces). Considere usar elementos interactivos nativos en su lugar.", messages: { 'Non-interactive <{0}> with tabindex="0" has no interactive role.': 'El elemento no interactivo <{0}> con tabindex="0" no tiene un rol interactivo.' } },
4602
+ "nested-interactive": { description: "Los controles interactivos no deben estar anidados dentro de otros.", guidance: "Anidar elementos interactivos (como un botón dentro de un enlace, o un enlace dentro de un botón) crea un comportamiento impredecible y confunde a las tecnologías de asistencia. El navegador puede eliminar el elemento interno del árbol de accesibilidad. Reestructure el HTML para que los elementos interactivos sean hermanos, no anidados. Si necesita una tarjeta clicable, use CSS y JavaScript en lugar de anidar.", messages: { "Interactive element <{0}> is nested inside <{1}>.": "El elemento interactivo <{0}> está anidado dentro de <{1}>." } },
4603
+ "scrollable-region-focusable": { description: "Las regiones desplazables deben ser accesibles por teclado.", guidance: "El contenido que se desplaza debe ser accesible para los usuarios de teclado. Si una región tiene overflow:scroll u overflow:auto y contiene contenido desplazable, necesita tabindex='0' para ser enfocable, o debe contener elementos enfocables. Sin esto, los usuarios de teclado no pueden desplazar el contenido.", messages: { "Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements.": "La región desplazable no es accesible por teclado. Agregue tabindex='0' o incluya elementos enfocables." } },
4604
+ accesskeys: { description: "Los valores del atributo accesskey deben ser únicos.", guidance: "Cuando múltiples elementos comparten la misma accesskey, el comportamiento del navegador se vuelve impredecible; generalmente solo se activa el primer elemento. Asegúrese de que cada valor de accesskey sea único dentro de la página. También considere que las accesskeys pueden entrar en conflicto con los atajos del navegador y del lector de pantalla, así que úselas con moderación.", messages: { 'Duplicate accesskey "{0}". Each accesskey must be unique.': 'Accesskey duplicada "{0}". Cada accesskey debe ser única.' } },
4605
+ "heading-order": { description: "Los niveles de encabezado deben incrementarse de uno en uno; saltarse niveles (por ejemplo, h2 a h4) dificulta la navegación.", guidance: "Los usuarios de lectores de pantalla navegan por encabezados para comprender la estructura de la página. Saltarse niveles (h2 a h4) sugiere contenido faltante y crea confusión. Comience con h1 para el título de la página, luego use h2 para secciones principales, h3 para subsecciones, etc. Puede volver a subir (h3 a h2) al comenzar una nueva sección.", messages: { "Heading level {0} skipped from level {1}.": "Nivel de encabezado {0} saltado desde el nivel {1}." } },
4606
+ "empty-heading": { description: "Los encabezados deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por encabezados, por lo que los encabezados vacíos crean puntos de navegación confusos. Asegúrese de que todos los encabezados contengan texto visible o nombres accesibles. Si un encabezado se usa puramente para estilo visual, use CSS en lugar de elementos de encabezado.", messages: { "Heading is empty. Add text content or remove the heading element.": "El encabezado está vacío. Agregue contenido de texto o elimine el elemento de encabezado." } },
4607
+ "p-as-heading": { description: "Los párrafos no deben estilizarse para parecer encabezados.", guidance: "Cuando los párrafos se estilizan con negrita y fuentes grandes para parecer encabezados, los usuarios de lectores de pantalla pierden la estructura semántica. Use elementos de encabezado apropiados (h1-h6) en lugar de párrafos estilizados. Si necesita un estilo específico, aplique CSS a los elementos de encabezado manteniendo la jerarquía adecuada de encabezados.", messages: { "Paragraph appears to be styled as a heading. Use an h1-h6 element instead.": "El párrafo parece estar estilizado como un encabezado. Use un elemento h1-h6 en su lugar." } },
4608
+ "landmark-one-main": { description: "La página debe tener exactamente un landmark main.", guidance: "El landmark main contiene el contenido principal de la página. Los lectores de pantalla permiten a los usuarios saltar directamente al contenido principal. Use un solo elemento <main> (o role='main') para envolver el contenido central, excluyendo encabezados, pies de página y navegación.", messages: { "Page has no main landmark.": "La página no tiene un landmark main.", "Page has multiple main landmarks.": "La página tiene múltiples landmarks main." } },
4609
+ "landmark-no-duplicate-banner": { description: "La página no debe tener más de un landmark banner.", guidance: "El landmark banner (típicamente <header>) identifica contenido orientado al sitio como logotipos y búsqueda. Solo se permite un banner de nivel superior por página. Si necesita múltiples encabezados, anídelos dentro de elementos de sección (article, section, aside) donde se convierten en encabezados de alcance en lugar de banners de nivel de página.", messages: { "Page has multiple banner landmarks.": "La página tiene múltiples landmarks banner." } },
4610
+ "landmark-no-duplicate-contentinfo": { description: "La página no debe tener más de un landmark contentinfo.", guidance: "El landmark contentinfo (típicamente <footer>) contiene información sobre la página como derechos de autor e información de contacto. Solo se permite un contentinfo de nivel superior por página. Anide pies de página adicionales dentro de elementos de sección para delimitar su alcance.", messages: { "Page has multiple contentinfo landmarks.": "La página tiene múltiples landmarks contentinfo." } },
4611
+ "landmark-no-duplicate-main": { description: "La página no debe tener más de un landmark main.", guidance: "Solo debe existir un landmark main por página. El landmark main identifica el área de contenido principal. Si tiene múltiples secciones de contenido, use <section> con encabezados apropiados en lugar de múltiples elementos main.", messages: { "Page has multiple main landmarks.": "La página tiene múltiples landmarks main." } },
4612
+ "landmark-banner-is-top-level": { description: "El landmark banner no debe estar anidado dentro de otro landmark.", guidance: "El landmark banner debe ser un landmark de nivel superior, no anidado dentro de article, aside, main, nav o section. Si un header está dentro de estos elementos, automáticamente se convierte en un encabezado genérico en lugar de un banner. Elimine el role='banner' explícito de los headers anidados o reestructure la página.", messages: { "Banner landmark is nested within another landmark.": "El landmark banner está anidado dentro de otro landmark." } },
4613
+ "landmark-contentinfo-is-top-level": { description: "El landmark contentinfo no debe estar anidado dentro de otro landmark.", guidance: "El landmark contentinfo debe ser un landmark de nivel superior. Un footer dentro de article, aside, main, nav o section se convierte en un pie de página de alcance, no en un landmark contentinfo. Elimine el role='contentinfo' explícito de los footers anidados o mueva el footer fuera de los elementos de sección.", messages: { "Contentinfo landmark is nested within another landmark.": "El landmark contentinfo está anidado dentro de otro landmark." } },
4614
+ "landmark-main-is-top-level": { description: "El landmark main no debe estar anidado dentro de otro landmark.", guidance: "El landmark main debe ser un landmark de nivel superior ya que representa el contenido principal de la página. No anide <main> o role='main' dentro de elementos article, aside, nav o section.", messages: { "Main landmark is nested within another landmark.": "El landmark main está anidado dentro de otro landmark." } },
4615
+ "landmark-complementary-is-top-level": { description: "El landmark aside (complementary) debe ser de nivel superior o estar directamente dentro de main.", guidance: "El landmark complementary (aside) debe ser de nivel superior o un hijo directo de main. Anidar aside profundamente dentro de otros landmarks reduce su descubrimiento para los usuarios de lectores de pantalla que navegan por landmarks.", messages: { "Complementary landmark should be top-level.": "El landmark complementary debe ser de nivel superior." } },
4616
+ "landmark-unique": { description: "Los landmarks deben tener etiquetas únicas cuando hay múltiples del mismo tipo.", guidance: "Cuando una página tiene múltiples landmarks del mismo tipo (por ejemplo, múltiples elementos nav), cada uno debe tener un nombre accesible único mediante aria-label o aria-labelledby. Esto ayuda a los usuarios de lectores de pantalla a distinguirlos (por ejemplo, 'Navegación principal' vs 'Navegación del pie de página').", messages: { 'Multiple {0} landmarks have the same label "{1}".': 'Múltiples landmarks {0} tienen la misma etiqueta "{1}".', "Multiple {0} landmarks have no label. Add unique aria-label attributes.": "Múltiples landmarks {0} no tienen etiqueta. Agregue atributos aria-label únicos." } },
4617
+ region: { description: "Todo el contenido de la página debe estar contenido dentro de landmarks.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por landmarks. El contenido fuera de los landmarks es más difícil de encontrar y comprender. Envuelva todo el contenido visible en landmarks apropiados: <header>, <nav>, <main>, <aside>, <footer>, o <section> con una etiqueta. Los enlaces de salto pueden existir fuera de los landmarks.", messages: { "Content is not contained within a landmark region.": "El contenido no está contenido dentro de una región landmark." } },
4618
+ list: { description: "<ul> y <ol> solo deben contener <li>, <script> o <template> como hijos directos.", guidance: "Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, reestructure su CSS para estilizar los elementos <li> directamente.", messages: { "List contains non-<li> child <{0}>.": "La lista contiene un hijo no <li>: <{0}>." } },
4619
+ listitem: { description: "Los elementos <li> deben estar contenidos en un <ul>, <ol> o <menu>.", guidance: "Los elementos de lista (<li>) solo tienen significado semántico dentro de un contenedor de lista (<ul>, <ol> o <menu>). Fuera de estos contenedores, las tecnologías de asistencia no pueden transmitir la relación de lista. Envuelva los elementos <li> en el contenedor de lista apropiado.", messages: { "<li> is not contained in a <ul>, <ol>, or <menu>.": "<li> no está contenido en un <ul>, <ol> o <menu>." } },
4620
+ dlitem: { description: "Los elementos <dt> y <dd> deben estar contenidos en un <dl>.", guidance: "Los términos de definición (<dt>) y las definiciones (<dd>) solo tienen significado semántico dentro de una lista de definiciones (<dl>). Fuera de <dl>, se tratan como texto genérico. Envuelva los pares relacionados de <dt> y <dd> en un elemento <dl> para transmitir la relación término/definición a las tecnologías de asistencia.", messages: { "<{0}> is not contained in a <dl>.": "<{0}> no está contenido en un <dl>." } },
4621
+ "definition-list": { description: "Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script> o <template>.", guidance: "Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o reestructure usando el marcado adecuado de lista de definiciones.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contiene un hijo inválido <{0}>." } },
4622
+ "aria-roles": { description: "Los valores de rol ARIA deben ser válidos.", guidance: "Los valores de rol inválidos son ignorados por las tecnologías de asistencia, lo que significa que el elemento no tendrá la semántica prevista. Verifique la ortografía y use solo roles definidos en la especificación WAI-ARIA. Los roles comunes incluyen: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.", messages: { 'Invalid ARIA role "{0}".': 'Rol ARIA inválido "{0}".' } },
4623
+ "aria-valid-attr": { description: "Los atributos ARIA deben ser válidos (correctamente escritos).", guidance: "Los atributos ARIA mal escritos son ignorados por las tecnologías de asistencia. Verifique la ortografía contra la especificación WAI-ARIA. Errores comunes: aria-labeledby (debe ser aria-labelledby), aria-role (debe ser role), aria-description (válido en ARIA 1.3+).", messages: { 'Invalid ARIA attribute "{0}".': 'Atributo ARIA inválido "{0}".' } },
4624
+ "aria-valid-attr-value": { description: "Los atributos ARIA deben tener valores válidos.", guidance: "Cada atributo ARIA acepta tipos de valores específicos. Los atributos booleanos (aria-hidden, aria-disabled) aceptan solo 'true' o 'false'. Los atributos triestado (aria-checked, aria-pressed) también aceptan 'mixed'. Los atributos de token (aria-live, aria-autocomplete) aceptan valores predefinidos. Los atributos de referencia de ID (aria-labelledby, aria-describedby) deben referenciar IDs de elementos existentes.", messages: { '{0} must be "true" or "false", got "{1}".': '{0} debe ser "true" o "false", se obtuvo "{1}".', '{0} must be "true", "false", or "mixed", got "{1}".': '{0} debe ser "true", "false" o "mixed", se obtuvo "{1}".', '{0} must be an integer, got "{1}".': '{0} debe ser un entero, se obtuvo "{1}".', '{0} must be a number, got "{1}".': '{0} debe ser un número, se obtuvo "{1}".', 'Invalid value "{0}" for {1}.': 'Valor inválido "{0}" para {1}.' } },
4625
+ "aria-required-attr": { description: "Los elementos con roles ARIA deben tener todos los atributos ARIA requeridos.", guidance: "Algunos roles ARIA requieren atributos específicos para funcionar correctamente. Por ejemplo, checkbox requiere aria-checked, slider requiere aria-valuenow, heading requiere aria-level. Sin estos atributos, las tecnologías de asistencia no pueden transmitir el estado o valor del elemento a los usuarios. Agregue el atributo requerido faltante con un valor apropiado.", messages: { 'Role "{0}" requires attribute "{1}".': 'El rol "{0}" requiere el atributo "{1}".' } },
4626
+ "aria-allowed-attr": { description: "Los atributos ARIA deben estar permitidos para el rol del elemento.", guidance: "Cada rol ARIA admite atributos específicos. Usar atributos no admitidos crea confusión para las tecnologías de asistencia. Consulte la especificación ARIA para saber qué atributos son válidos para cada rol, o elimine el atributo si no es necesario.", messages: { 'ARIA attribute "{0}" is not allowed on role "{1}".': 'El atributo ARIA "{0}" no está permitido en el rol "{1}".' } },
4627
+ "aria-allowed-role": { description: "El rol ARIA debe ser apropiado para el elemento.", guidance: "No todos los roles ARIA se pueden aplicar a todos los elementos HTML. Muchos elementos tienen roles implícitos (por ejemplo, <header> es implícitamente banner, <nav> es navigation, <main> es main). Agregar un rol explícito que coincida con el rol implícito es redundante. Agregar un rol conflictivo rompe la semántica. Elimine el atributo role o use un elemento diferente.", messages: { "Element <{0}> should not have an explicit role.": "El elemento <{0}> no debería tener un rol explícito.", 'Role "{0}" is not allowed on element <{1}>.': 'El rol "{0}" no está permitido en el elemento <{1}>.' } },
4628
+ "aria-required-children": { description: "Ciertos roles ARIA requieren que estén presentes roles hijos específicos.", guidance: "Algunos roles ARIA representan contenedores que deben contener roles hijos específicos para una semántica adecuada. Por ejemplo, una lista debe contener listitems, un menú debe contener menuitems. Agregue los elementos hijos requeridos con roles apropiados, o use elementos HTML nativos que proporcionen esta semántica implícitamente (por ejemplo, <ul> con <li>).", messages: { 'Role "{0}" requires children with role: {1}.': 'El rol "{0}" requiere hijos con rol: {1}.' } },
4629
+ "aria-required-parent": { description: "Ciertos roles ARIA deben estar contenidos dentro de roles padre específicos.", guidance: "Algunos roles ARIA representan elementos que deben existir dentro de roles de contenedor específicos. Por ejemplo, un listitem debe estar dentro de una lista, un tab debe estar dentro de un tablist. Envuelva el elemento en el padre apropiado, o use elementos HTML nativos que proporcionen esta estructura (por ejemplo, <li> dentro de <ul>).", messages: { 'Role "{0}" must be contained within: {1}.': 'El rol "{0}" debe estar contenido dentro de: {1}.' } },
4630
+ "aria-hidden-body": { description: "aria-hidden='true' no debe estar presente en el body del documento.", guidance: "Establecer aria-hidden='true' en el elemento body oculta todo el contenido de la página de las tecnologías de asistencia, haciendo la página completamente inaccesible para los usuarios de lectores de pantalla. Elimine aria-hidden del elemento body. Si necesita ocultar contenido temporalmente (por ejemplo, detrás de un modal), use aria-hidden en secciones específicas en su lugar.", messages: { "aria-hidden='true' on body hides all content from assistive technologies.": "aria-hidden='true' en el body oculta todo el contenido de las tecnologías de asistencia." } },
4631
+ "aria-hidden-focus": { description: "Los elementos con aria-hidden='true' no deben contener elementos enfocables.", guidance: "Cuando aria-hidden='true' oculta un elemento de las tecnologías de asistencia pero el elemento contiene hijos enfocables, los usuarios de teclado pueden enfocar esos hijos pero los usuarios de lectores de pantalla no sabrán que existen. Elimine los elementos enfocables de la región oculta, agregue tabindex='-1' a ellos, o elimine aria-hidden.", messages: { "Focusable element is inside an aria-hidden region.": "Un elemento enfocable está dentro de una región aria-hidden." } },
4632
+ "aria-command-name": { description: "Los comandos ARIA deben tener un nombre accesible.", guidance: "Los roles de comando ARIA interactivos (button, link, menuitem) deben tener nombres accesibles para que los usuarios sepan qué acción realizan. Agregue contenido de texto visible, aria-label o aria-labelledby para proporcionar un nombre.", messages: { "ARIA command has no accessible name.": "El comando ARIA no tiene nombre accesible." } },
4633
+ "aria-input-field-name": { description: "Los campos de entrada ARIA deben tener un nombre accesible.", guidance: "Los widgets de entrada ARIA (combobox, listbox, searchbox, slider, spinbutton, textbox) deben tener nombres accesibles para que los usuarios comprendan qué datos ingresar. Agregue una etiqueta visible con aria-labelledby, o use aria-label si una etiqueta visible no es posible.", messages: { "ARIA input field has no accessible name.": "El campo de entrada ARIA no tiene nombre accesible." } },
4634
+ "aria-toggle-field-name": { description: "Los campos de alternancia ARIA deben tener un nombre accesible.", guidance: "Los controles de alternancia ARIA (checkbox, switch, radio, menuitemcheckbox, menuitemradio) deben tener nombres accesibles para que los usuarios comprendan qué opción están seleccionando. Agregue contenido de texto visible, aria-label, o use aria-labelledby para referenciar una etiqueta visible.", messages: { "ARIA toggle field has no accessible name.": "El campo de alternancia ARIA no tiene nombre accesible." } },
4635
+ "aria-meter-name": { description: "Los elementos meter ARIA deben tener un nombre accesible.", guidance: "Los elementos meter muestran un valor dentro de un rango conocido (como uso de disco o fortaleza de contraseña). Deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué se está midiendo. Use aria-label o aria-labelledby para proporcionar contexto.", messages: { "Meter has no accessible name.": "El meter no tiene nombre accesible." } },
4636
+ "aria-progressbar-name": { description: "Los elementos progressbar ARIA deben tener un nombre accesible.", guidance: "Los indicadores de progreso deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan qué proceso se está rastreando. Use aria-label (por ejemplo, 'Progreso de carga de archivo') o aria-labelledby para referenciar un encabezado o etiqueta visible.", messages: { "Progressbar has no accessible name.": "La barra de progreso no tiene nombre accesible." } },
4637
+ "aria-dialog-name": { description: "Los diálogos ARIA deben tener un nombre accesible.", guidance: "Los elementos dialog y alertdialog deben tener nombres accesibles para que los usuarios de lectores de pantalla comprendan el propósito del diálogo cuando se abre. Use aria-label o aria-labelledby apuntando al encabezado del diálogo. Los elementos nativos <dialog> también deben tener un nombre accesible.", messages: { "Dialog has no accessible name.": "El diálogo no tiene nombre accesible." } },
4638
+ "aria-tooltip-name": { description: "Los tooltips ARIA deben tener un nombre accesible.", guidance: "Los elementos tooltip deben tener nombres accesibles (generalmente su contenido de texto). El contenido del tooltip típicamente sirve como el nombre accesible. Asegúrese de que el tooltip contenga contenido de texto descriptivo o tenga aria-label.", messages: { "Tooltip has no accessible name.": "El tooltip no tiene nombre accesible." } },
4639
+ "aria-treeitem-name": { description: "Los elementos treeitem ARIA deben tener un nombre accesible.", guidance: "Los elementos de árbol deben tener nombres accesibles para que los usuarios de lectores de pantalla puedan comprender la estructura del árbol y navegarlo eficazmente. Proporcione contenido de texto, aria-label o aria-labelledby para cada treeitem.", messages: { "Treeitem has no accessible name.": "El treeitem no tiene nombre accesible." } },
4640
+ "aria-prohibited-attr": { description: "Los atributos ARIA no deben estar prohibidos para el rol del elemento.", guidance: "Algunos roles ARIA prohíben ciertos atributos. Por ejemplo, roles como 'none', 'presentation', 'generic' y roles de nivel de texto (code, emphasis, strong) prohíben aria-label y aria-labelledby porque el nombramiento no está soportado para estos roles. Elimine los atributos prohibidos o cambie el rol.", messages: { "aria-label and aria-labelledby are prohibited on <{0}> elements.": "aria-label y aria-labelledby están prohibidos en elementos <{0}>.", 'aria-label and aria-labelledby are prohibited on role "{0}".': 'aria-label y aria-labelledby están prohibidos en el rol "{0}".', 'Attribute "{0}" is prohibited on role "{1}".': 'El atributo "{0}" está prohibido en el rol "{1}".' } },
4641
+ "presentation-role-conflict": { description: "Los elementos con role='presentation' o role='none' no deben ser enfocables ni tener atributos ARIA globales.", guidance: "Cuando un elemento tiene role='presentation' o role='none', está marcado como decorativo y se elimina del árbol de accesibilidad. Sin embargo, si el elemento es enfocable o tiene ciertos atributos ARIA, el rol de presentación se ignora y el elemento permanece accesible. Esto crea confusión. Elimine el rol de presentación, o elimine la enfocabilidad/atributos ARIA.", messages: { "Presentation role conflicts with: {0}. The role will be ignored.": "El rol de presentación entra en conflicto con: {0}. El rol será ignorado.", 'Element with implicit presentation role (alt="") conflicts with: {0}. The decorative role will be ignored.': 'El elemento con rol de presentación implícito (alt="") entra en conflicto con: {0}. El rol decorativo será ignorado.' } },
4642
+ "button-name": { description: "Los botones deben tener texto discernible.", guidance: "Los usuarios de lectores de pantalla necesitan saber qué hace un botón. Agregue contenido de texto visible, aria-label o aria-labelledby. Para botones de icono, use aria-label describiendo la acción (por ejemplo, aria-label='Cerrar'). Si el botón contiene una imagen, asegúrese de que la imagen tenga texto alt describiendo la acción del botón.", messages: { "Button has no discernible text.": "El botón no tiene texto discernible." } },
4643
+ "summary-name": { description: "Los elementos <summary> deben tener un nombre accesible.", guidance: "El elemento <summary> proporciona la etiqueta visible para un widget de divulgación <details>. Debe tener contenido de texto descriptivo para que los usuarios de lectores de pantalla comprendan qué se revelará al expandirse. Agregue texto claro y conciso que indique qué contenido está en la sección de detalles.", messages: { "<summary> element has no accessible name. Add descriptive text.": "El elemento <summary> no tiene nombre accesible. Agregue texto descriptivo." } },
4644
+ "link-name": { description: "Los enlaces deben tener texto discernible mediante contenido, aria-label o aria-labelledby.", guidance: "Los usuarios de lectores de pantalla necesitan saber a dónde lleva un enlace. Agregue contenido de texto descriptivo, aria-label, o use aria-labelledby. Para enlaces de imagen, asegúrese de que la imagen tenga texto alt describiendo el destino del enlace. Evite texto genérico como 'haga clic aquí' o 'leer más': el texto del enlace debe tener sentido fuera de contexto.", messages: { "Link has no discernible text.": "El enlace no tiene texto discernible." } },
4645
+ "skip-link": { description: "Los enlaces de salto deben apuntar a un destino válido en la página.", guidance: "Los enlaces de salto permiten a los usuarios de teclado omitir la navegación repetitiva y saltar directamente al contenido principal. El enlace de salto debe ser el primer elemento enfocable en la página, enlazar al contenido principal (por ejemplo, href='#main'), y hacerse visible cuando se enfoca. Puede estar visualmente oculto hasta enfocarse usando CSS.", messages: { 'Skip link points to "#{0}" which does not exist on the page.': 'El enlace de salto apunta a "#{0}" que no existe en la página.' } },
4646
+ "link-in-text-block": { description: "Los enlaces dentro de bloques de texto deben distinguirse por algo más que solo el color.", guidance: "Los usuarios que no pueden percibir diferencias de color necesitan otras señales visuales para identificar enlaces. Los enlaces en texto deben tener subrayados u otros indicadores no cromáticos. Si usa solo color, asegure un contraste de 3:1 con el texto circundante Y proporcione una indicación adicional al enfocar/pasar el cursor.", messages: { "Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.": "El enlace en el bloque de texto no es visualmente distinguible del texto circundante. Agregue un indicador visual no cromático como un subrayado o borde." } },
4647
+ "html-has-lang": { description: "El elemento <html> debe tener un atributo lang.", guidance: "Los lectores de pantalla usan el atributo lang para determinar qué reglas de idioma y pronunciación usar. Sin él, el contenido puede pronunciarse incorrectamente. Establezca lang en el idioma principal de la página (por ejemplo, lang='en' para inglés, lang='es' para español).", messages: { "<html> element missing lang attribute.": "Al elemento <html> le falta el atributo lang." } },
4648
+ "html-lang-valid": { description: "El atributo lang en <html> debe tener un valor válido.", guidance: "El atributo lang debe usar una etiqueta de idioma BCP 47 válida. Use un código de idioma de 2 o 3 letras (por ejemplo, 'en', 'fr', 'zh'), opcionalmente seguido de un código de región (por ejemplo, 'en-US', 'pt-BR'). Las etiquetas inválidas impiden que los lectores de pantalla pronuncien correctamente el contenido.", messages: { 'Invalid lang attribute value "{0}".': 'Valor de atributo lang inválido "{0}".' } },
4649
+ "valid-lang": { description: "El atributo lang debe tener un valor válido en todos los elementos.", guidance: "Cuando aparece contenido en un idioma diferente dentro de una página (por ejemplo, una cita en francés en un documento en inglés), envuélvalo con un atributo lang para asegurar la pronunciación correcta. El valor de lang debe ser una etiqueta BCP 47 válida. Códigos comunes: en, es, fr, de, zh, ja, pt, ar, ru.", messages: { "Empty lang attribute value.": "Valor de atributo lang vacío.", 'Invalid lang attribute value "{0}".': 'Valor de atributo lang inválido "{0}".' } },
4650
+ "html-xml-lang-mismatch": { description: "Los atributos lang y xml:lang en <html> deben coincidir.", guidance: "En documentos XHTML, si tanto lang como xml:lang están presentes, deben especificar el mismo idioma base. Los valores no coincidentes confunden a las tecnologías de asistencia. Elimine xml:lang (preferido para HTML5) o asegúrese de que ambos atributos tengan valores idénticos.", messages: { 'lang="{0}" and xml:lang="{1}" do not match.': 'lang="{0}" y xml:lang="{1}" no coinciden.' } },
4651
+ "td-headers-attr": { description: "Todas las celdas en una tabla que usan el atributo headers deben referenciar IDs de encabezado válidos.", guidance: "El atributo headers en las celdas de tabla debe referenciar IDs de celdas de encabezado (th o td) dentro de la misma tabla. Esto crea asociaciones explícitas para lectores de pantalla. Verifique que todos los IDs referenciados existan y estén escritos correctamente. Para tablas simples, considere usar scope en elementos th en su lugar.", messages: { 'Headers attribute references the cell itself ("{0}").': 'El atributo headers referencia a la propia celda ("{0}").', 'Headers attribute references non-existent ID "{0}".': 'El atributo headers referencia un ID inexistente "{0}".' } },
4652
+ "th-has-data-cells": { description: "Los encabezados de tabla deben estar asociados con celdas de datos.", guidance: "Una tabla con celdas de encabezado (th) pero sin celdas de datos (td) probablemente es un mal uso del marcado de tabla para diseño o tiene contenido faltante. Agregue celdas de datos que los encabezados describan, o use marcado apropiado que no sea de tabla si estos no son datos tabulares.", messages: { "Table has header cells but no data cells.": "La tabla tiene celdas de encabezado pero no celdas de datos." } },
4653
+ "td-has-header": { description: "Las celdas de datos en tablas mayores de 3x3 deben tener encabezados asociados.", guidance: "En tablas complejas, los usuarios de lectores de pantalla necesitan asociaciones de encabezados para comprender las celdas de datos. Use elementos th con atributo scope, o el atributo headers en elementos td. Para tablas simples (3x3 o menos), esto es menos crítico ya que el contexto generalmente es claro.", messages: { "Data cell has no associated header. Add th elements with scope, or headers attribute.": "La celda de datos no tiene encabezado asociado. Agregue elementos th con scope o el atributo headers." } },
4654
+ "scope-attr-valid": { description: "El atributo scope en los encabezados de tabla debe tener un valor válido.", guidance: "El atributo scope indica a los lectores de pantalla a qué celdas se aplica un encabezado. Los valores válidos son: row, col, rowgroup, colgroup. Usar valores inválidos rompe la asociación entre encabezados y celdas.", messages: { 'Invalid scope value "{0}". Use row, col, rowgroup, or colgroup.': 'Valor de scope inválido "{0}". Use row, col, rowgroup o colgroup.' } },
4655
+ "empty-table-header": { description: "Las celdas de encabezado de tabla deben tener texto visible.", guidance: "Los encabezados de tabla vacíos no proporcionan información a los usuarios de lectores de pantalla. Agregue texto descriptivo al encabezado, o si el encabezado está intencionalmente vacío (como una celda de esquina), considere usar un elemento td en su lugar o agregar una etiqueta visualmente oculta.", messages: { "Table header cell is empty. Add text or use aria-label.": "La celda de encabezado de tabla está vacía. Agregue texto o use aria-label." } },
4656
+ "duplicate-id-aria": { description: "Los IDs usados en asociaciones ARIA y label deben ser únicos para evitar referencias rotas.", guidance: "Cuando aria-labelledby, aria-describedby, aria-controls o label[for] referencian un ID duplicado, solo se usa el primer elemento coincidente. Esto rompe la relación prevista y puede dejar controles sin nombre o descripciones faltantes. Asegúrese de que los IDs referenciados por atributos ARIA y asociaciones de etiquetas sean únicos en todo el documento.", messages: { 'Duplicate ID "{0}" referenced by {1}.': 'ID duplicado "{0}" referenciado por {1}.' } },
4657
+ "video-caption": { description: "Los elementos de video deben tener subtítulos mediante <track kind='captions'>.", guidance: "Los subtítulos proporcionan alternativas de texto para el contenido de audio en videos, beneficiando a usuarios sordos y aquellos que no pueden escuchar el audio. Agregue un elemento <track> con kind='captions' apuntando a un archivo de subtítulos WebVTT. Los subtítulos deben incluir tanto el diálogo como los efectos de sonido importantes.", messages: { "Video element has no captions track.": "El elemento de video no tiene pista de subtítulos." } },
4658
+ "audio-caption": { description: "Los elementos de audio deben tener una alternativa de texto o transcripción.", guidance: "El contenido solo de audio como podcasts o grabaciones necesita una alternativa de texto para usuarios sordos. Proporcione una transcripción en la misma página o enlazada cerca. La transcripción debe incluir todo el contenido hablado y descripciones de sonidos relevantes.", messages: { "Audio element has no transcript or text alternative. Add a transcript or track element.": "El elemento de audio no tiene transcripción o alternativa de texto. Agregue una transcripción o elemento track." } },
4659
+ "color-contrast": { description: "Los elementos de texto deben tener suficiente contraste de color contra el fondo.", guidance: "WCAG SC 1.4.3 requiere una relación de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (>=24px o >=18.66px en negrita). Aumente el contraste oscureciendo el texto o aclarando el fondo, o viceversa.", messages: { "Insufficient color contrast ratio of {0}:1 (required {1}:1).": "Relación de contraste de color insuficiente de {0}:1 (requerido {1}:1)." } }
4614
4660
  };
4615
4661
  export {
4616
- Se as clearAllCaches,
4617
- Pe as clearAriaAttrAuditCache,
4618
- Re as clearAriaHiddenCache,
4662
+ Ee as clearAllCaches,
4663
+ Be as clearAriaAttrAuditCache,
4664
+ Me as clearAriaHiddenCache,
4619
4665
  Oe as clearColorCaches,
4620
- qe as clearComputedRoleCache,
4621
- I as compileDeclarativeRule,
4622
- Wn as configureRules,
4623
- Fn as createChunkedAudit,
4666
+ Le as clearComputedRoleCache,
4667
+ E as compileDeclarativeRule,
4668
+ Vn as configureRules,
4669
+ _n as createChunkedAudit,
4624
4670
  v as getAccessibleName,
4625
4671
  A as getAccessibleTextContent,
4626
- J as getActiveRules,
4627
- R as getComputedRole,
4672
+ Z as getActiveRules,
4673
+ N as getComputedRole,
4628
4674
  u as getHtmlSnippet,
4629
- ge as getImplicitRole,
4630
- On as getRuleById,
4675
+ ve as getImplicitRole,
4676
+ Yn as getRuleById,
4631
4677
  m as getSelector,
4632
4678
  b as isAriaHidden,
4633
- Ce as isValidRole,
4634
- Bn as localeEn,
4635
- Un as localeEs,
4636
- Dn as querySelectorShadowAware,
4637
- zn as registerLocale,
4638
- xe as rules,
4639
- Pn as runAudit,
4640
- jn as validateDeclarativeRule
4679
+ Ne as isValidRole,
4680
+ Xn as localeEn,
4681
+ Kn as localeEs,
4682
+ Un as querySelectorShadowAware,
4683
+ Bn as registerLocale,
4684
+ Se as rules,
4685
+ Gn as runAudit,
4686
+ Ae as translateViolations,
4687
+ On as validateDeclarativeRule
4641
4688
  };