agemon 6.0.0 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +424 -89
  2. package/assets/skills/design/agemon-design/NOTICE.md +34 -0
  3. package/assets/skills/design/agemon-design/SKILL.md +95 -358
  4. package/assets/skills/design/agemon-design/references/actions.md +215 -0
  5. package/assets/skills/design/agemon-design/references/brief.md +13 -9
  6. package/assets/skills/design/agemon-design/references/core-rules.md +166 -0
  7. package/assets/skills/design/agemon-design/references/craft-intent.md +16 -14
  8. package/assets/skills/design/agemon-design/references/dashboard.md +1 -1
  9. package/assets/skills/design/agemon-design/references/dataviz.md +8 -8
  10. package/assets/skills/design/agemon-design/references/finish-bar.md +1 -1
  11. package/assets/skills/design/agemon-design/references/heuristics.md +56 -140
  12. package/assets/skills/design/agemon-design/references/inspiration.md +6 -6
  13. package/assets/skills/design/agemon-design/references/loops.md +9 -9
  14. package/assets/skills/design/agemon-design/references/motion.md +1 -1
  15. package/assets/skills/design/agemon-design/references/personas.md +4 -4
  16. package/assets/skills/design/{agemon-design-dashboard/SKILL.md → agemon-design/references/preset-dashboard.md} +8 -12
  17. package/assets/skills/design/{agemon-design-editorial/SKILL.md → agemon-design/references/preset-editorial.md} +5 -9
  18. package/assets/skills/design/{agemon-design-minimal/SKILL.md → agemon-design/references/preset-minimal.md} +8 -12
  19. package/assets/skills/design/agemon-design/references/principles-catalog.md +3 -3
  20. package/assets/skills/design/agemon-design/references/recipe-auth.md +3 -3
  21. package/assets/skills/design/agemon-design/references/recipe-dashboard.md +2 -2
  22. package/assets/skills/design/agemon-design/references/recipe-landing.md +9 -11
  23. package/assets/skills/design/agemon-design/references/review.md +7 -7
  24. package/assets/skills/design/agemon-design/references/routing.md +143 -0
  25. package/assets/skills/design/agemon-design/references/spec.md +8 -8
  26. package/assets/skills/design/agemon-design/references/stack.md +1 -1
  27. package/assets/skills/design/agemon-design/references/stage-assets.md +106 -0
  28. package/assets/skills/design/agemon-design/references/stage-build.md +46 -0
  29. package/assets/skills/design/agemon-design/references/stage-critique.md +34 -0
  30. package/assets/skills/design/agemon-design/references/stage-directions.md +44 -0
  31. package/assets/skills/design/agemon-design/references/stage-intake.md +34 -0
  32. package/assets/skills/design/agemon-design/references/stage-verify.md +44 -0
  33. package/assets/skills/design/agemon-design/references/state-design.md +1 -1
  34. package/assets/skills/design/agemon-design/references/tells-2026-11.md +32 -0
  35. package/assets/skills/design/agemon-design/references/themes.md +24 -20
  36. package/assets/skills/design/agemon-design/references/third-party-licenses.md +323 -0
  37. package/assets/skills/design/agemon-design/references/typography.md +11 -9
  38. package/assets/skills/design/agemon-design/scripts/audit-page.js +532 -0
  39. package/assets/skills/design/agemon-design/scripts/check-source.mjs +358 -0
  40. package/assets/skills/design/agemon-design/scripts/deal.mjs +231 -0
  41. package/assets/skills/design/agemon-design/scripts/inventory.mjs +676 -0
  42. package/assets/skills/{design/agemon-design-dashboard → essentials/web-design-guidelines}/LICENSE +1 -1
  43. package/assets/skills/essentials/web-design-guidelines/NOTICE.md +14 -0
  44. package/assets/skills/essentials/web-design-guidelines/SKILL.md +18 -0
  45. package/assets/skills/essentials/web-design-guidelines/references/guidelines.md +184 -0
  46. package/assets/skills/{design/agemon-design-minimal → essentials/writing-guidelines}/LICENSE +1 -1
  47. package/assets/skills/essentials/writing-guidelines/NOTICE.md +14 -0
  48. package/assets/skills/essentials/writing-guidelines/SKILL.md +18 -0
  49. package/assets/skills/essentials/writing-guidelines/references/guidelines.md +250 -0
  50. package/assets/skills/founder/agemon-founder-board/LICENSE +21 -0
  51. package/assets/skills/founder/agemon-founder-board/NOTICE.md +11 -0
  52. package/assets/skills/founder/agemon-founder-board/SKILL.md +111 -0
  53. package/assets/skills/founder/agemon-founder-board/lenses.md +84 -0
  54. package/assets/skills/founder/agemon-founder-brand/LICENSE +21 -0
  55. package/assets/skills/founder/agemon-founder-brand/NOTICE.md +12 -0
  56. package/assets/skills/founder/agemon-founder-brand/SKILL.md +77 -0
  57. package/assets/skills/founder/agemon-founder-cfo/LICENSE +21 -0
  58. package/assets/skills/founder/agemon-founder-cfo/NOTICE.md +12 -0
  59. package/assets/skills/founder/agemon-founder-cfo/SKILL.md +109 -0
  60. package/assets/skills/founder/agemon-founder-cfo/example.json +27 -0
  61. package/assets/skills/founder/agemon-founder-cfo/unit_economics.py +270 -0
  62. package/assets/skills/founder/agemon-founder-competitors/LICENSE +21 -0
  63. package/assets/skills/founder/agemon-founder-competitors/NOTICE.md +12 -0
  64. package/assets/skills/founder/agemon-founder-competitors/SKILL.md +84 -0
  65. package/assets/skills/founder/agemon-founder-consumer/LICENSE +21 -0
  66. package/assets/skills/founder/agemon-founder-consumer/NOTICE.md +11 -0
  67. package/assets/skills/founder/agemon-founder-consumer/SKILL.md +126 -0
  68. package/assets/skills/founder/agemon-founder-consumer/customer.example.json +34 -0
  69. package/assets/skills/founder/agemon-founder-consumer/panel.py +424 -0
  70. package/assets/skills/founder/agemon-founder-consumer/pitch.example.md +3 -0
  71. package/assets/skills/founder/agemon-founder-launch/LICENSE +21 -0
  72. package/assets/skills/founder/agemon-founder-launch/NOTICE.md +11 -0
  73. package/assets/skills/founder/agemon-founder-launch/SKILL.md +65 -0
  74. package/assets/skills/founder/agemon-founder-marketing/LICENSE +21 -0
  75. package/assets/skills/founder/agemon-founder-marketing/NOTICE.md +11 -0
  76. package/assets/skills/founder/agemon-founder-marketing/SKILL.md +80 -0
  77. package/assets/skills/founder/agemon-founder-offer/LICENSE +21 -0
  78. package/assets/skills/founder/agemon-founder-offer/NOTICE.md +11 -0
  79. package/assets/skills/founder/agemon-founder-offer/SKILL.md +75 -0
  80. package/assets/skills/founder/agemon-founder-ops/LICENSE +21 -0
  81. package/assets/skills/founder/agemon-founder-ops/NOTICE.md +12 -0
  82. package/assets/skills/founder/agemon-founder-ops/SKILL.md +77 -0
  83. package/assets/skills/founder/agemon-founder-plan/LICENSE +21 -0
  84. package/assets/skills/founder/agemon-founder-plan/NOTICE.md +11 -0
  85. package/assets/skills/founder/agemon-founder-plan/SKILL.md +88 -0
  86. package/assets/skills/founder/agemon-founder-plan/compile.py +178 -0
  87. package/assets/skills/founder/agemon-founder-pricing/LICENSE +21 -0
  88. package/assets/skills/founder/agemon-founder-pricing/NOTICE.md +12 -0
  89. package/assets/skills/founder/agemon-founder-pricing/SKILL.md +92 -0
  90. package/assets/skills/founder/agemon-founder-pricing/van_westendorp.py +172 -0
  91. package/assets/skills/{design/agemon-design-editorial → output-style/agemon-adhd}/LICENSE +1 -1
  92. package/assets/skills/output-style/agemon-adhd/NOTICE.md +13 -0
  93. package/assets/skills/output-style/agemon-adhd/SKILL.md +138 -0
  94. package/assets/skills/security/agemon-security-audit/AI-AND-LLM.md +83 -0
  95. package/assets/skills/security/agemon-security-audit/ATTACK-CLASSES.md +130 -0
  96. package/assets/skills/security/agemon-security-audit/CLIENT-SIDE.md +83 -0
  97. package/assets/skills/security/agemon-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
  98. package/assets/skills/security/agemon-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
  99. package/assets/skills/security/agemon-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
  100. package/assets/skills/security/agemon-security-audit/HUNTING.md +251 -0
  101. package/assets/skills/security/agemon-security-audit/LICENSE +21 -0
  102. package/assets/skills/security/agemon-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
  103. package/assets/skills/security/agemon-security-audit/NOTICE.md +12 -0
  104. package/assets/skills/security/agemon-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
  105. package/assets/skills/security/agemon-security-audit/RECONNAISSANCE.md +156 -0
  106. package/assets/skills/security/agemon-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
  107. package/assets/skills/security/agemon-security-audit/SKILL.md +192 -0
  108. package/assets/skills/security/agemon-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
  109. package/assets/skills/security/agemon-security-audit/VALIDATION-AND-REPORTING.md +186 -0
  110. package/assets/skills/security/agemon-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
  111. package/assets/skills/security/agemon-security-audit/report-schema.json +461 -0
  112. package/assets/skills/security/agemon-security-audit/validate-coverage-ledger.cjs +872 -0
  113. package/assets/skills/security/agemon-security-audit/validate-findings.cjs +773 -0
  114. package/assets/skills/workflow/agemon-brainstorming/LICENSE +21 -0
  115. package/assets/skills/workflow/agemon-brainstorming/NOTICE.md +14 -0
  116. package/assets/skills/workflow/agemon-brainstorming/SKILL.md +287 -0
  117. package/assets/skills/workflow/agemon-brainstorming/builder-check-prompt.md +23 -0
  118. package/assets/skills/workflow/agemon-dispatching-parallel-agents/LICENSE +21 -0
  119. package/assets/skills/workflow/agemon-dispatching-parallel-agents/NOTICE.md +13 -0
  120. package/assets/skills/workflow/agemon-dispatching-parallel-agents/SKILL.md +167 -0
  121. package/assets/skills/workflow/agemon-executing-plans/LICENSE +21 -0
  122. package/assets/skills/workflow/agemon-executing-plans/NOTICE.md +15 -0
  123. package/assets/skills/workflow/agemon-executing-plans/SKILL.md +322 -0
  124. package/assets/skills/workflow/agemon-executing-plans/scripts/task-done +55 -0
  125. package/assets/skills/workflow/agemon-executing-plans/scripts/task-start +32 -0
  126. package/assets/skills/workflow/agemon-finishing-a-development-branch/LICENSE +21 -0
  127. package/assets/skills/workflow/agemon-finishing-a-development-branch/NOTICE.md +13 -0
  128. package/assets/skills/workflow/agemon-finishing-a-development-branch/SKILL.md +225 -0
  129. package/assets/skills/workflow/agemon-subagent-driven-development/LICENSE +21 -0
  130. package/assets/skills/workflow/agemon-subagent-driven-development/NOTICE.md +15 -0
  131. package/assets/skills/workflow/agemon-subagent-driven-development/SKILL.md +339 -0
  132. package/assets/skills/workflow/agemon-subagent-driven-development/code-reviewer.md +198 -0
  133. package/assets/skills/workflow/agemon-subagent-driven-development/implementer-prompt.md +154 -0
  134. package/assets/skills/workflow/agemon-subagent-driven-development/re-review-prompt.md +116 -0
  135. package/assets/skills/workflow/agemon-subagent-driven-development/references/batching.md +11 -0
  136. package/assets/skills/workflow/agemon-subagent-driven-development/references/cannot-verify.md +10 -0
  137. package/assets/skills/workflow/agemon-subagent-driven-development/references/final-review.md +49 -0
  138. package/assets/skills/workflow/agemon-subagent-driven-development/references/fix-loop.md +77 -0
  139. package/assets/skills/workflow/agemon-subagent-driven-development/references/handling-reports.md +15 -0
  140. package/assets/skills/workflow/agemon-subagent-driven-development/references/preflight.md +26 -0
  141. package/assets/skills/workflow/agemon-subagent-driven-development/references/resuming.md +12 -0
  142. package/assets/skills/workflow/agemon-subagent-driven-development/references/role-scout.md +18 -0
  143. package/assets/skills/workflow/agemon-subagent-driven-development/references/role-worker.md +19 -0
  144. package/assets/skills/workflow/agemon-subagent-driven-development/scripts/review-package +64 -0
  145. package/assets/skills/workflow/agemon-subagent-driven-development/scripts/sdd-repo +54 -0
  146. package/assets/skills/workflow/agemon-subagent-driven-development/scripts/sdd-workspace +110 -0
  147. package/assets/skills/workflow/agemon-subagent-driven-development/scripts/task-brief +108 -0
  148. package/assets/skills/workflow/agemon-subagent-driven-development/task-reviewer-prompt.md +209 -0
  149. package/assets/skills/workflow/agemon-test-driven-development/LICENSE +21 -0
  150. package/assets/skills/workflow/agemon-test-driven-development/NOTICE.md +14 -0
  151. package/assets/skills/workflow/agemon-test-driven-development/SKILL.md +330 -0
  152. package/assets/skills/workflow/agemon-test-driven-development/writing-good-tests.md +198 -0
  153. package/assets/skills/workflow/agemon-using-git-worktrees/LICENSE +21 -0
  154. package/assets/skills/workflow/agemon-using-git-worktrees/NOTICE.md +13 -0
  155. package/assets/skills/workflow/agemon-using-git-worktrees/SKILL.md +167 -0
  156. package/assets/skills/workflow/agemon-writing-plans/LICENSE +21 -0
  157. package/assets/skills/workflow/agemon-writing-plans/NOTICE.md +13 -0
  158. package/assets/skills/workflow/agemon-writing-plans/SKILL.md +204 -0
  159. package/dist/{chunk-5LATHJYU.js → chunk-ZZBF3GOV.js} +369 -944
  160. package/dist/chunk-ZZBF3GOV.js.map +1 -0
  161. package/dist/{fake-subprocess-2BETWZVZ.js → fake-subprocess-IV4ML77S.js} +3 -33
  162. package/dist/fake-subprocess-IV4ML77S.js.map +1 -0
  163. package/dist/index.js +3477 -731
  164. package/dist/index.js.map +1 -1
  165. package/package.json +8 -1
  166. package/assets/skills/design/agemon-design/references/agents.md +0 -63
  167. package/assets/skills/design/agemon-design-dashboard/NOTICE.md +0 -9
  168. package/assets/skills/design/agemon-design-editorial/NOTICE.md +0 -9
  169. package/assets/skills/design/agemon-design-minimal/NOTICE.md +0 -9
  170. package/dist/chunk-5LATHJYU.js.map +0 -1
  171. package/dist/fake-subprocess-2BETWZVZ.js.map +0 -1
@@ -0,0 +1,532 @@
1
+ (() => {
2
+ // Rendered checks for one viewport. Evaluate this whole file as an expression in the page,
3
+ // at each width, with any browser tool; its value is the report as a plain object.
4
+ // It only reads computed styles and layout. It returns numbers, typed values and selectors,
5
+ // never page text, because the page is untrusted.
6
+ // Outside a page it returns its calculations instead, so they can be tested without a browser.
7
+
8
+ const BODY_TEXT_CONTRAST = 4.5;
9
+ const LARGE_TEXT_CONTRAST = 3;
10
+ // WCAG large text is 18pt, or 14pt bold, at 96 CSS pixels to 72 points.
11
+ const LARGE_TEXT_PX = 24;
12
+ const LARGE_BOLD_TEXT_PX = 18.66;
13
+ const BOLD_WEIGHT = 700;
14
+ const TARGET_MIN_PX = 24;
15
+ const TARGET_CIRCLE_RADIUS_PX = TARGET_MIN_PX / 2;
16
+ const NATIVE_TOGGLE_MIN_PX = 12;
17
+ const NATIVE_TOGGLE_MAX_PX = 16;
18
+ const RECT_TOLERANCE_PX = 1;
19
+
20
+ const MAX_ELEMENTS = 20000;
21
+ const MAX_LAYERS = 2000;
22
+ const MAX_TARGETS = 1000;
23
+ const MAX_LISTED = 100;
24
+ const MAX_DEPTH = 256;
25
+ const MAX_TEXT_NODES = 1000;
26
+ const MAX_AUDIT_MS = 20000;
27
+ const MAX_FONT_WORDS = 4;
28
+ const MAX_ID_WORDS = 4;
29
+
30
+ const RGB_COLOR =
31
+ /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+)(%?))?\s*\)$/;
32
+ const SAFE_ID = /^[A-Za-z][\w-]{0,31}$/;
33
+ const SAFE_TAG = /^[a-z][a-z0-9]*(?:-[a-z0-9]+){0,2}$/;
34
+ const SAFE_FONT_FAMILY = /^[A-Za-z][A-Za-z0-9 -]{0,47}$/;
35
+ const SAFE_RADIUS = /^[\d.]+(?:px|%)(?: [\d.]+(?:px|%))?$/;
36
+ const PSEUDO_TEXT_CONTENT = /^(?!""$)(?:"|attr\(|counter)/;
37
+ const WHITE_CANVAS = { r: 255, g: 255, b: 255, a: 1 };
38
+ const NO_EFFECT = { filter: "none", backdropFilter: "none", mixBlendMode: "normal" };
39
+
40
+ const INTERACTIVE_SELECTOR = [
41
+ "a[href]", "button", "input:not([type='hidden'])", "select", "textarea", "summary",
42
+ "[tabindex]:not([tabindex='-1'])",
43
+ ...["button", "link", "checkbox", "radio", "switch", "tab", "menuitem", "option"].map(
44
+ (role) => `[role='${role}']`,
45
+ ),
46
+ ].join(",");
47
+ const DISABLED_SELECTOR = ":disabled, [aria-disabled='true']";
48
+ const VALUE_TEXT_CONTROLS =
49
+ "input:not([type='checkbox'], [type='radio'], [type='range'], [type='color'], [type='file'], [type='hidden'], [type='image']), textarea, select";
50
+ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
51
+ const MEDIA_TAGS = new Set(["IMG", "VIDEO", "CANVAS", "PICTURE", "svg"]);
52
+ const OPAQUE_TO_AUDIT_TAGS = new Set(["IFRAME", "FRAME", "OBJECT", "EMBED", "CANVAS"]);
53
+ const PAGE_LEVEL_TAGS = new Set(["html", "body"]);
54
+
55
+ function parseRgb(value) {
56
+ const match = RGB_COLOR.exec(value);
57
+ if (!match) return null;
58
+ const alpha = match[4] === undefined ? 1 : Number(match[4]) / (match[5] ? 100 : 1);
59
+ return { r: Number(match[1]), g: Number(match[2]), b: Number(match[3]), a: alpha };
60
+ }
61
+
62
+ function linearChannel(channel) {
63
+ const scaled = channel / 255;
64
+ return scaled <= 0.04045 ? scaled / 12.92 : ((scaled + 0.055) / 1.055) ** 2.4;
65
+ }
66
+
67
+ function relativeLuminance({ r, g, b }) {
68
+ return 0.2126 * linearChannel(r) + 0.7152 * linearChannel(g) + 0.0722 * linearChannel(b);
69
+ }
70
+
71
+ function contrastRatio(first, second) {
72
+ const [lighter, darker] = [relativeLuminance(first), relativeLuminance(second)].sort(
73
+ (left, right) => right - left,
74
+ );
75
+ return (lighter + 0.05) / (darker + 0.05);
76
+ }
77
+
78
+ // WCAG thresholds are exact: a reported 4.50 must never hide a real 4.499.
79
+ function flooredRatio(ratio) {
80
+ return Math.floor(ratio * 100) / 100;
81
+ }
82
+
83
+ function compositeOver(top, bottom) {
84
+ const blend = (channel) => top[channel] * top.a + bottom[channel] * (1 - top.a);
85
+ return { r: blend("r"), g: blend("g"), b: blend("b"), a: 1 };
86
+ }
87
+
88
+ function requiredContrast(fontSizePx, fontWeight) {
89
+ const isLarge =
90
+ fontSizePx >= LARGE_TEXT_PX || (fontSizePx >= LARGE_BOLD_TEXT_PX && fontWeight >= BOLD_WEIGHT);
91
+ return isLarge ? LARGE_TEXT_CONTRAST : BODY_TEXT_CONTRAST;
92
+ }
93
+
94
+ function rectsTouch(first, second) {
95
+ return (
96
+ first.x < second.x + second.w &&
97
+ second.x < first.x + first.w &&
98
+ first.y < second.y + second.h &&
99
+ second.y < first.y + first.h
100
+ );
101
+ }
102
+
103
+ function rectHolds(outer, inner) {
104
+ return (
105
+ inner.x >= outer.x - RECT_TOLERANCE_PX &&
106
+ inner.y >= outer.y - RECT_TOLERANCE_PX &&
107
+ inner.x + inner.w <= outer.x + outer.w + RECT_TOLERANCE_PX &&
108
+ inner.y + inner.h <= outer.y + outer.h + RECT_TOLERANCE_PX
109
+ );
110
+ }
111
+
112
+ function changesPaintedColor(style) {
113
+ return (
114
+ Number(style.opacity) < 1 ||
115
+ Object.entries(NO_EFFECT).some(([property, inert]) => (style[property] ?? inert) !== inert)
116
+ );
117
+ }
118
+
119
+ // Returns the backgrounds, nearest first and ending in an opaque one, or why it cannot know them.
120
+ function readBackgrounds(text, readColor) {
121
+ const backgrounds = [];
122
+ let reachedOpaque = false;
123
+ for (const [level, style] of text.chain.entries()) {
124
+ if (changesPaintedColor(style)) return { reason: "opacity-or-effect" };
125
+ if (reachedOpaque) continue;
126
+ if (style.backgroundImage !== "none") return { reason: "image-or-gradient" };
127
+ const background = readColor(style.backgroundColor);
128
+ if (!background) return { reason: "unreadable-colour" };
129
+ const box = text.backgroundRects?.[level];
130
+ if (background.a > 0 && box && !text.textRects.every((rect) => rectHolds(box, rect))) {
131
+ return { reason: "text-outside-background" };
132
+ }
133
+ backgrounds.push(background);
134
+ reachedOpaque = background.a === 1;
135
+ }
136
+ if (!reachedOpaque) {
137
+ const isDark = text.canvasIsDark || text.chain.at(-1).colorScheme?.includes("dark");
138
+ if (isDark) return { reason: "canvas-colour" };
139
+ backgrounds.push(WHITE_CANVAS);
140
+ }
141
+ return { backgrounds };
142
+ }
143
+
144
+ function assessTextContrast(text, readColor = parseRgb) {
145
+ const touchesLayer = text.layerRects.some((layer) =>
146
+ text.textRects.some((rect) => rectsTouch(layer, rect)),
147
+ );
148
+ if (touchesLayer) return { status: "not-verified", reason: "overlapping-layer" };
149
+ const foreground = readColor(text.color);
150
+ if (!foreground) return { status: "not-verified", reason: "unreadable-colour" };
151
+ if (foreground.a === 0) return { status: "not-verified", reason: "transparent-text" };
152
+ const { backgrounds, reason } = readBackgrounds(text, readColor);
153
+ if (reason) return { status: "not-verified", reason };
154
+
155
+ const background = backgrounds.reduceRight((below, above) => compositeOver(above, below));
156
+ const ratio = contrastRatio(compositeOver(foreground, background), background);
157
+ const required = requiredContrast(text.fontSizePx, text.fontWeight);
158
+ return ratio < required
159
+ ? { status: "failed", ratio: flooredRatio(ratio), required }
160
+ : { status: "passed" };
161
+ }
162
+
163
+ function distanceToRect(pointX, pointY, rect) {
164
+ const dx = Math.max(rect.x - pointX, 0, pointX - (rect.x + rect.w));
165
+ const dy = Math.max(rect.y - pointY, 0, pointY - (rect.y + rect.h));
166
+ return Math.hypot(dx, dy);
167
+ }
168
+
169
+ function isUndersized({ w, h }) {
170
+ return w < TARGET_MIN_PX || h < TARGET_MIN_PX;
171
+ }
172
+
173
+ // SC 2.5.8's spacing exception: a circle around an undersized target must touch no other
174
+ // target and no other undersized target's circle.
175
+ function hasSpacingException(target, targets) {
176
+ const centerX = target.x + target.w / 2;
177
+ const centerY = target.y + target.h / 2;
178
+ return targets.every((other) => {
179
+ if (other === target) return true;
180
+ if (distanceToRect(centerX, centerY, other) < TARGET_CIRCLE_RADIUS_PX) return false;
181
+ if (!isUndersized(other)) return true;
182
+ const otherCenterX = other.x + other.w / 2;
183
+ const otherCenterY = other.y + other.h / 2;
184
+ return Math.hypot(centerX - otherCenterX, centerY - otherCenterY) >= TARGET_MIN_PX;
185
+ });
186
+ }
187
+
188
+ function undersizedTargets(targets) {
189
+ return targets
190
+ .filter((target) => isUndersized(target) && !target.exempt)
191
+ .filter((target) => !hasSpacingException(target, targets))
192
+ .map(({ selector, w, h }) => ({ selector, w, h }));
193
+ }
194
+
195
+ function horizontalOverflow({ scrollWidth, clientWidth, elements }) {
196
+ if (scrollWidth <= clientWidth) return [];
197
+ const offenders = elements
198
+ .filter(({ right }) => right > clientWidth)
199
+ .sort((left, right) => right.right - left.right)
200
+ .map(({ selector, right }) => ({ selector, by: right - clientWidth }));
201
+ return [{ selector: "html", by: scrollWidth - clientWidth }, ...offenders].slice(0, MAX_LISTED);
202
+ }
203
+
204
+ function firstFontFamily(stack) {
205
+ const family = stack.split(",")[0].trim().replace(/^["']|["']$/g, "");
206
+ const isSafe = SAFE_FONT_FAMILY.test(family) && family.split(" ").length <= MAX_FONT_WORDS;
207
+ return isSafe ? family : null;
208
+ }
209
+
210
+ function isSafeId(id) {
211
+ return SAFE_ID.test(id) && id.split(/[-_]/).length <= MAX_ID_WORDS;
212
+ }
213
+
214
+ function hexOf({ r, g, b, a }) {
215
+ const hexPair = (value) => Math.round(value).toString(16).padStart(2, "0");
216
+ const alpha = a < 1 ? hexPair(a * 255) : "";
217
+ return `#${hexPair(r)}${hexPair(g)}${hexPair(b)}${alpha}`;
218
+ }
219
+
220
+ function createColorReader() {
221
+ const context = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
222
+ const known = new Map();
223
+ // A detached canvas turns oklch, lab and other computed forms into sRGB for the formula.
224
+ const readThroughCanvas = (value) => {
225
+ if (!context || !CSS.supports("color", value)) return null;
226
+ context.clearRect(0, 0, 1, 1);
227
+ context.fillStyle = value;
228
+ context.fillRect(0, 0, 1, 1);
229
+ const [r, g, b, alpha] = context.getImageData(0, 0, 1, 1).data;
230
+ return { r, g, b, a: alpha / 255 };
231
+ };
232
+ return (value) => {
233
+ if (!known.has(value)) known.set(value, parseRgb(value) ?? readThroughCanvas(value));
234
+ return known.get(value);
235
+ };
236
+ }
237
+
238
+ // A form control named parentElement shadows the real property and can loop forever,
239
+ // so the walk stops at a fixed depth.
240
+ function ancestorsOf(element) {
241
+ const ancestors = [];
242
+ for (let current = element; current && ancestors.length < MAX_DEPTH; current = current.parentElement) {
243
+ ancestors.push(current);
244
+ }
245
+ return ancestors;
246
+ }
247
+
248
+ function selectorStep(element) {
249
+ const tag = SAFE_TAG.test(element.localName) ? element.localName : "*";
250
+ let position = 1;
251
+ for (let sibling = element.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
252
+ if (sibling.localName === element.localName) position += 1;
253
+ }
254
+ return `${tag}:nth-of-type(${position})`;
255
+ }
256
+
257
+ function selectorOf(element) {
258
+ const steps = [];
259
+ for (const current of ancestorsOf(element)) {
260
+ if (PAGE_LEVEL_TAGS.has(current.localName)) break;
261
+ const id = current.getAttribute("id");
262
+ if (id && isSafeId(id) && document.querySelectorAll(`[id="${id}"]`).length === 1) {
263
+ return [`#${id}`, ...steps].join(" > ");
264
+ }
265
+ steps.unshift(selectorStep(current));
266
+ }
267
+ return ["body", ...steps].join(" > ");
268
+ }
269
+
270
+ function plainRect(rect) {
271
+ return { x: rect.left, y: rect.top, w: rect.width, h: rect.height };
272
+ }
273
+
274
+ function isBlankOrNotText(node) {
275
+ return node.nodeType !== Node.TEXT_NODE || !/\S/.test(node.data);
276
+ }
277
+
278
+ function hasOwnText(element) {
279
+ return [...element.childNodes].some((node) => !isBlankOrNotText(node));
280
+ }
281
+
282
+ function ownTextRects(element) {
283
+ const range = document.createRange();
284
+ const rects = [];
285
+ for (const node of [...element.childNodes].slice(0, MAX_TEXT_NODES)) {
286
+ if (isBlankOrNotText(node)) continue;
287
+ range.selectNode(node);
288
+ for (const rect of range.getClientRects()) rects.push(plainRect(rect));
289
+ }
290
+ return rects;
291
+ }
292
+
293
+ // Rects of 1 pixel or less, or moved off the page, hold visually hidden text and controls.
294
+ function isRendered(style, rect) {
295
+ return (
296
+ style.visibility !== "hidden" && Number(style.opacity) > 0 && rect.width > 1 && rect.height > 1 &&
297
+ rect.right + window.scrollX > 0 && rect.bottom + window.scrollY > 0
298
+ );
299
+ }
300
+
301
+ function isHiddenByAncestor(element, rect) {
302
+ return ancestorsOf(element).slice(1).some((ancestor) => {
303
+ if (PAGE_LEVEL_TAGS.has(ancestor.localName)) return false;
304
+ const style = getComputedStyle(ancestor);
305
+ if (Number(style.opacity) === 0) return true;
306
+ const clips = style.overflowX !== "visible" || style.overflowY !== "visible";
307
+ return clips && !rectsTouch(plainRect(ancestor.getBoundingClientRect()), plainRect(rect));
308
+ });
309
+ }
310
+
311
+ function countValue(counts, value) {
312
+ counts.set(value, (counts.get(value) ?? 0) + 1);
313
+ }
314
+
315
+ function ranked(counts, key) {
316
+ return [...counts]
317
+ .sort((left, right) => right[1] - left[1])
318
+ .slice(0, MAX_LISTED)
319
+ .map(([value, count]) => ({ [key]: value, count }));
320
+ }
321
+
322
+ function isClippedByAncestor(element) {
323
+ return ancestorsOf(element).slice(1).some(
324
+ (ancestor) =>
325
+ // The browser moves these two's overflow to the viewport, so they do not clip here.
326
+ !PAGE_LEVEL_TAGS.has(ancestor.localName) && getComputedStyle(ancestor).overflowX !== "visible",
327
+ );
328
+ }
329
+
330
+ function isInSentence(element) {
331
+ for (const current of ancestorsOf(element)) {
332
+ if (getComputedStyle(current).display !== "inline") return false;
333
+ if (current.parentElement && hasOwnText(current.parentElement)) return true;
334
+ }
335
+ return false;
336
+ }
337
+
338
+ // The SC's exceptions for a link inside a sentence and for a browser-drawn control. An author
339
+ // who resized a toggle loses the exception, and the default size is the only sign of that.
340
+ function isExemptTarget(element, style, rect) {
341
+ const isNativeToggle =
342
+ element.matches("input[type='checkbox'], input[type='radio']") &&
343
+ style.appearance !== "none" &&
344
+ [rect.width, rect.height].every((side) => side >= NATIVE_TOGGLE_MIN_PX && side <= NATIVE_TOGGLE_MAX_PX);
345
+ return isNativeToggle || isInSentence(element);
346
+ }
347
+
348
+ // A label over a visually hidden control is what the pointer actually hits.
349
+ function isLabelForHiddenControl(element) {
350
+ const control = element.localName === "label" ? element.control : null;
351
+ return Boolean(control) && !isRendered(getComputedStyle(control), control.getBoundingClientRect());
352
+ }
353
+
354
+ function isPainted(style, readColor) {
355
+ return style.backgroundImage !== "none" || (readColor(style.backgroundColor)?.a ?? 1) > 0;
356
+ }
357
+
358
+ function pseudoStyles(element) {
359
+ return ["::before", "::after"]
360
+ .map((pseudo) => getComputedStyle(element, pseudo))
361
+ .filter(({ content }) => content !== "none" && content !== "normal");
362
+ }
363
+
364
+ function isCustomElementWithNothingToRead(element) {
365
+ return element.localName.includes("-") && element.childElementCount === 0 && !hasOwnText(element);
366
+ }
367
+
368
+ function auditPage() {
369
+ const deadline = performance.now() + MAX_AUDIT_MS;
370
+ const readColor = createColorReader();
371
+ const allElements = [...document.querySelectorAll("body *")];
372
+ const elements = allElements.slice(0, MAX_ELEMENTS);
373
+ const scan = {
374
+ elements: elements.length,
375
+ contrastChecked: 0,
376
+ truncated: elements.length < allElements.length,
377
+ };
378
+ const isOutOfTime = () => {
379
+ const outOfTime = performance.now() > deadline;
380
+ scan.truncated ||= outOfTime;
381
+ return outOfTime;
382
+ };
383
+ const addListed = (list, makeEntry) => {
384
+ if (list.length < MAX_LISTED) list.push(makeEntry());
385
+ else scan.truncated = true;
386
+ };
387
+
388
+ const root = document.documentElement;
389
+ const notVerified = [];
390
+ const contrast = [];
391
+ const targets = [];
392
+ const layers = [];
393
+ const textElements = [];
394
+ const fonts = new Map();
395
+ const palette = new Map();
396
+ const radii = new Map();
397
+ const reportNotVerified = (element, check, reason) =>
398
+ addListed(notVerified, () => ({ selector: selectorOf(element), check, reason }));
399
+ const countColor = (value) => {
400
+ const color = readColor(value);
401
+ if (color && color.a > 0) countValue(palette, hexOf(color));
402
+ };
403
+
404
+ for (const element of elements) {
405
+ if (isOutOfTime()) break;
406
+ const style = getComputedStyle(element);
407
+ if (style.display === "none") continue;
408
+ const rect = element.getBoundingClientRect();
409
+
410
+ if (element.shadowRoot) reportNotVerified(element, "all", "shadow-root");
411
+ const showsText = style.display === "contents" ? hasOwnText(element) : isRendered(style, rect);
412
+ if (!showsText) continue;
413
+ if (isHiddenByAncestor(element, rect)) continue;
414
+
415
+ if (OPAQUE_TO_AUDIT_TAGS.has(element.tagName)) reportNotVerified(element, "all", "embedded-document");
416
+ if (isCustomElementWithNothingToRead(element)) reportNotVerified(element, "all", "custom-element");
417
+
418
+ const pseudos = pseudoStyles(element);
419
+ if (pseudos.some(({ content }) => PSEUDO_TEXT_CONTENT.test(content))) {
420
+ reportNotVerified(element, "contrast", "pseudo-element-text");
421
+ }
422
+ const paintsPseudo = pseudos.some((pseudo) => isPainted(pseudo, readColor));
423
+ if (paintsPseudo || MEDIA_TAGS.has(element.tagName) || isPainted(style, readColor)) {
424
+ layers.push({ element, rect: plainRect(rect), coversOwnText: paintsPseudo });
425
+ }
426
+
427
+ const isTarget = element.matches(INTERACTIVE_SELECTOR) || isLabelForHiddenControl(element);
428
+ if (isTarget && !element.closest(DISABLED_SELECTOR)) {
429
+ targets.push({ selector: element, exempt: isExemptTarget(element, style, rect), ...plainRect(rect) });
430
+ }
431
+
432
+ if (element.scrollWidth > element.clientWidth + 1 && ["auto", "scroll"].includes(style.overflowX)) {
433
+ reportNotVerified(element, "overflow", "inner-scroller");
434
+ }
435
+ if (element.matches(VALUE_TEXT_CONTROLS)) {
436
+ if (element.value) textElements.push({ element, style, textRects: [plainRect(rect)] });
437
+ else if (element.placeholder) reportNotVerified(element, "contrast", "placeholder-text");
438
+ } else if (hasOwnText(element)) {
439
+ if (element.namespaceURI === SVG_NAMESPACE) reportNotVerified(element, "contrast", "svg-text");
440
+ else textElements.push({ element, style, textRects: ownTextRects(element) });
441
+ }
442
+
443
+ countColor(style.backgroundColor);
444
+ if (parseFloat(style.borderTopWidth) > 0) countColor(style.borderTopColor);
445
+ if (parseFloat(style.borderTopLeftRadius) > 0 && SAFE_RADIUS.test(style.borderRadius)) {
446
+ countValue(radii, style.borderRadius);
447
+ }
448
+ }
449
+ scan.truncated ||= layers.length > MAX_LAYERS || targets.length > MAX_TARGETS;
450
+ const checkedLayers = layers.slice(0, MAX_LAYERS);
451
+ const checkedTargets = targets.slice(0, MAX_TARGETS);
452
+ const canvasIsDark = Boolean(document.querySelector("meta[name='color-scheme']")?.content.includes("dark"));
453
+
454
+ for (const { element, style, textRects } of textElements) {
455
+ if (isOutOfTime()) break;
456
+ const family = firstFontFamily(style.fontFamily);
457
+ if (family) countValue(fonts, family);
458
+ countColor(style.color);
459
+
460
+ if (element.closest(DISABLED_SELECTOR)) continue;
461
+ const ancestors = ancestorsOf(element);
462
+ const result = assessTextContrast(
463
+ {
464
+ color: style.webkitTextFillColor || style.color,
465
+ fontSizePx: parseFloat(style.fontSize),
466
+ fontWeight: Number(style.fontWeight),
467
+ chain: ancestors.map((ancestor) => getComputedStyle(ancestor)),
468
+ backgroundRects: ancestors.map((ancestor) =>
469
+ PAGE_LEVEL_TAGS.has(ancestor.localName) ? null : plainRect(ancestor.getBoundingClientRect()),
470
+ ),
471
+ textRects,
472
+ layerRects: checkedLayers
473
+ .filter((layer) => layer.coversOwnText || !layer.element.contains(element))
474
+ .map((layer) => layer.rect),
475
+ canvasIsDark,
476
+ },
477
+ readColor,
478
+ );
479
+ if (result.status === "not-verified") {
480
+ reportNotVerified(element, "contrast", result.reason);
481
+ continue;
482
+ }
483
+ scan.contrastChecked += 1;
484
+ if (result.status === "failed") {
485
+ addListed(contrast, () => ({ selector: selectorOf(element), ratio: result.ratio, required: result.required }));
486
+ }
487
+ }
488
+
489
+ const pastViewport = elements
490
+ .filter((element) => element.getBoundingClientRect().right + window.scrollX > root.clientWidth)
491
+ .filter((element) => getComputedStyle(element).position !== "fixed" && !isClippedByAncestor(element))
492
+ .map((element) => ({ selector: element, right: element.getBoundingClientRect().right + window.scrollX }));
493
+ const pageClipsOverflow = [root, document.body].some((part) => getComputedStyle(part).overflowX !== "visible");
494
+ if (pageClipsOverflow && pastViewport.length > 0) {
495
+ addListed(notVerified, () => ({ selector: "html", check: "overflow", reason: "page-clips-overflow" }));
496
+ }
497
+ const located = (entry) => ({
498
+ ...entry,
499
+ selector: typeof entry.selector === "string" ? entry.selector : selectorOf(entry.selector),
500
+ });
501
+
502
+ return {
503
+ viewport: { width: root.clientWidth },
504
+ overflow: horizontalOverflow({
505
+ scrollWidth: pageClipsOverflow ? root.clientWidth : root.scrollWidth,
506
+ clientWidth: root.clientWidth,
507
+ elements: pastViewport,
508
+ }).map(located),
509
+ targets: undersizedTargets(checkedTargets).slice(0, MAX_LISTED).map(located),
510
+ contrast,
511
+ notVerified,
512
+ fonts: ranked(fonts, "family"),
513
+ palette: ranked(palette, "value"),
514
+ radii: ranked(radii, "value"),
515
+ scan,
516
+ };
517
+ }
518
+
519
+ if (typeof document !== "undefined") return auditPage();
520
+ return {
521
+ assessTextContrast,
522
+ contrastRatio,
523
+ firstFontFamily,
524
+ flooredRatio,
525
+ hexOf,
526
+ horizontalOverflow,
527
+ isSafeId,
528
+ parseRgb,
529
+ requiredContrast,
530
+ undersizedTargets,
531
+ };
532
+ })()