@setsquare/review-sdk 0.0.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 (181) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +57 -0
  3. package/README.md +338 -0
  4. package/THIRD-PARTY-NOTICES.md +61 -0
  5. package/dist/autoscan.d.ts +25 -0
  6. package/dist/autoscan.d.ts.map +1 -0
  7. package/dist/autoscan.js +332 -0
  8. package/dist/autoscan.js.map +1 -0
  9. package/dist/axe.d.ts +96 -0
  10. package/dist/axe.d.ts.map +1 -0
  11. package/dist/axe.js +210 -0
  12. package/dist/axe.js.map +1 -0
  13. package/dist/bin/setsquare.d.ts +3 -0
  14. package/dist/bin/setsquare.d.ts.map +1 -0
  15. package/dist/bin/setsquare.js +21 -0
  16. package/dist/bin/setsquare.js.map +1 -0
  17. package/dist/brand.d.ts +84 -0
  18. package/dist/brand.d.ts.map +1 -0
  19. package/dist/brand.js +335 -0
  20. package/dist/brand.js.map +1 -0
  21. package/dist/checks/dynamic.d.ts +57 -0
  22. package/dist/checks/dynamic.d.ts.map +1 -0
  23. package/dist/checks/dynamic.js +184 -0
  24. package/dist/checks/dynamic.js.map +1 -0
  25. package/dist/checks/rule-pack.d.ts +34 -0
  26. package/dist/checks/rule-pack.d.ts.map +1 -0
  27. package/dist/checks/rule-pack.js +270 -0
  28. package/dist/checks/rule-pack.js.map +1 -0
  29. package/dist/checks/versions.d.ts +28 -0
  30. package/dist/checks/versions.d.ts.map +1 -0
  31. package/dist/checks/versions.js +45 -0
  32. package/dist/checks/versions.js.map +1 -0
  33. package/dist/ci.d.ts +37 -0
  34. package/dist/ci.d.ts.map +1 -0
  35. package/dist/ci.js +199 -0
  36. package/dist/ci.js.map +1 -0
  37. package/dist/contract.d.ts +334 -0
  38. package/dist/contract.d.ts.map +1 -0
  39. package/dist/contract.js +35 -0
  40. package/dist/contract.js.map +1 -0
  41. package/dist/dedupe.d.ts +79 -0
  42. package/dist/dedupe.d.ts.map +1 -0
  43. package/dist/dedupe.js +102 -0
  44. package/dist/dedupe.js.map +1 -0
  45. package/dist/document.d.ts +29 -0
  46. package/dist/document.d.ts.map +1 -0
  47. package/dist/document.js +94 -0
  48. package/dist/document.js.map +1 -0
  49. package/dist/envelope.d.ts +124 -0
  50. package/dist/envelope.d.ts.map +1 -0
  51. package/dist/envelope.js +337 -0
  52. package/dist/envelope.js.map +1 -0
  53. package/dist/escape.d.ts +14 -0
  54. package/dist/escape.d.ts.map +1 -0
  55. package/dist/escape.js +20 -0
  56. package/dist/escape.js.map +1 -0
  57. package/dist/findings.d.ts +47 -0
  58. package/dist/findings.d.ts.map +1 -0
  59. package/dist/findings.js +229 -0
  60. package/dist/findings.js.map +1 -0
  61. package/dist/fixture.d.ts +7 -0
  62. package/dist/fixture.d.ts.map +1 -0
  63. package/dist/fixture.js +237 -0
  64. package/dist/fixture.js.map +1 -0
  65. package/dist/group.d.ts +214 -0
  66. package/dist/group.d.ts.map +1 -0
  67. package/dist/group.js +709 -0
  68. package/dist/group.js.map +1 -0
  69. package/dist/html-report.d.ts +68 -0
  70. package/dist/html-report.d.ts.map +1 -0
  71. package/dist/html-report.js +649 -0
  72. package/dist/html-report.js.map +1 -0
  73. package/dist/human.d.ts +65 -0
  74. package/dist/human.d.ts.map +1 -0
  75. package/dist/human.js +111 -0
  76. package/dist/human.js.map +1 -0
  77. package/dist/index.d.ts +34 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +33 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/infer.d.ts +65 -0
  82. package/dist/infer.d.ts.map +1 -0
  83. package/dist/infer.js +67 -0
  84. package/dist/infer.js.map +1 -0
  85. package/dist/insights.d.ts +140 -0
  86. package/dist/insights.d.ts.map +1 -0
  87. package/dist/insights.js +483 -0
  88. package/dist/insights.js.map +1 -0
  89. package/dist/install.d.ts +8 -0
  90. package/dist/install.d.ts.map +1 -0
  91. package/dist/install.js +114 -0
  92. package/dist/install.js.map +1 -0
  93. package/dist/links.d.ts +45 -0
  94. package/dist/links.d.ts.map +1 -0
  95. package/dist/links.js +45 -0
  96. package/dist/links.js.map +1 -0
  97. package/dist/mask.d.ts +85 -0
  98. package/dist/mask.d.ts.map +1 -0
  99. package/dist/mask.js +98 -0
  100. package/dist/mask.js.map +1 -0
  101. package/dist/observe.d.ts +51 -0
  102. package/dist/observe.d.ts.map +1 -0
  103. package/dist/observe.js +233 -0
  104. package/dist/observe.js.map +1 -0
  105. package/dist/options.d.ts +249 -0
  106. package/dist/options.d.ts.map +1 -0
  107. package/dist/options.js +185 -0
  108. package/dist/options.js.map +1 -0
  109. package/dist/output.d.ts +113 -0
  110. package/dist/output.d.ts.map +1 -0
  111. package/dist/output.js +231 -0
  112. package/dist/output.js.map +1 -0
  113. package/dist/perf.d.ts +11 -0
  114. package/dist/perf.d.ts.map +1 -0
  115. package/dist/perf.js +150 -0
  116. package/dist/perf.js.map +1 -0
  117. package/dist/reading-level.d.ts +73 -0
  118. package/dist/reading-level.d.ts.map +1 -0
  119. package/dist/reading-level.js +189 -0
  120. package/dist/reading-level.js.map +1 -0
  121. package/dist/reporter.d.ts +94 -0
  122. package/dist/reporter.d.ts.map +1 -0
  123. package/dist/reporter.js +709 -0
  124. package/dist/reporter.js.map +1 -0
  125. package/dist/rules.d.ts +55 -0
  126. package/dist/rules.d.ts.map +1 -0
  127. package/dist/rules.js +77 -0
  128. package/dist/rules.js.map +1 -0
  129. package/dist/runtime.d.ts +32 -0
  130. package/dist/runtime.d.ts.map +1 -0
  131. package/dist/runtime.js +67 -0
  132. package/dist/runtime.js.map +1 -0
  133. package/dist/sanitise.d.ts +116 -0
  134. package/dist/sanitise.d.ts.map +1 -0
  135. package/dist/sanitise.js +247 -0
  136. package/dist/sanitise.js.map +1 -0
  137. package/dist/scan.d.ts +60 -0
  138. package/dist/scan.d.ts.map +1 -0
  139. package/dist/scan.js +246 -0
  140. package/dist/scan.js.map +1 -0
  141. package/dist/screens.d.ts +98 -0
  142. package/dist/screens.d.ts.map +1 -0
  143. package/dist/screens.js +301 -0
  144. package/dist/screens.js.map +1 -0
  145. package/dist/settle.d.ts +65 -0
  146. package/dist/settle.d.ts.map +1 -0
  147. package/dist/settle.js +162 -0
  148. package/dist/settle.js.map +1 -0
  149. package/dist/shots.d.ts +109 -0
  150. package/dist/shots.d.ts.map +1 -0
  151. package/dist/shots.js +113 -0
  152. package/dist/shots.js.map +1 -0
  153. package/dist/style.d.ts +47 -0
  154. package/dist/style.d.ts.map +1 -0
  155. package/dist/style.js +154 -0
  156. package/dist/style.js.map +1 -0
  157. package/dist/suite.d.ts +218 -0
  158. package/dist/suite.d.ts.map +1 -0
  159. package/dist/suite.js +219 -0
  160. package/dist/suite.js.map +1 -0
  161. package/dist/summary.d.ts +268 -0
  162. package/dist/summary.d.ts.map +1 -0
  163. package/dist/summary.js +1308 -0
  164. package/dist/summary.js.map +1 -0
  165. package/dist/unscannable.d.ts +75 -0
  166. package/dist/unscannable.d.ts.map +1 -0
  167. package/dist/unscannable.js +111 -0
  168. package/dist/unscannable.js.map +1 -0
  169. package/dist/versions.d.ts +13 -0
  170. package/dist/versions.d.ts.map +1 -0
  171. package/dist/versions.js +39 -0
  172. package/dist/versions.js.map +1 -0
  173. package/dist/wcag-map.d.ts +19 -0
  174. package/dist/wcag-map.d.ts.map +1 -0
  175. package/dist/wcag-map.js +52 -0
  176. package/dist/wcag-map.js.map +1 -0
  177. package/dist/with-setsquare.d.ts +26 -0
  178. package/dist/with-setsquare.d.ts.map +1 -0
  179. package/dist/with-setsquare.js +70 -0
  180. package/dist/with-setsquare.js.map +1 -0
  181. package/package.json +81 -0
@@ -0,0 +1,189 @@
1
+ import readability from 'text-readability';
2
+ import { sanitiseComputed } from './findings.js';
3
+ import { READING_LEVEL_HELP_URL } from './links.js';
4
+ /**
5
+ * WCAG 3.1.5 Reading Level (AAA), as an honest heuristic.
6
+ *
7
+ * The SC's normative bar is text readable "without lower secondary education"
8
+ * — ISCED level 2, roughly nine years of schooling — measured *after removing
9
+ * proper names and titles*, and satisfiable by supplemental content or a
10
+ * simpler alternate version. No formula measures any of that. What formulas
11
+ * measure is word and sentence statistics, which approximate a reading grade,
12
+ * which approximates the bar. Three honesty rules follow:
13
+ *
14
+ * 1. **Never a violation.** The finding is always `incomplete` — a person
15
+ * decides 3.1.5, this check only tells them where to look.
16
+ * 2. **Fire only above a margin.** Automated scoring cannot remove proper
17
+ * names, which skews grades HIGH, so the finding fires a full grade above
18
+ * the target (FK > 10 against a target of 9; LIX > 48 against 44). A page
19
+ * at 9.4 is not a finding; a page at 11 is worth a person's time.
20
+ * 3. **Language-gated.** Flesch–Kincaid is calibrated for English; LIX is
21
+ * the conventional measure for the Nordic languages (thresholds are
22
+ * convention, not W3C-blessed: <35 easy, 45+ difficult, "lower
23
+ * secondary" ≈ 44). This is the same routing Sa11y documents publicly —
24
+ * the behaviour, never its GPL code. An unsupported or missing `lang`
25
+ * skips silently: missing `lang` is already an axe violation (3.1.1),
26
+ * and scoring text in a language the formula was never calibrated for
27
+ * would manufacture a number.
28
+ *
29
+ * Extraction happens in-page (main content only — scoring navigation chrome
30
+ * would grade the menu, not the writing); scoring happens Node-side in the
31
+ * worker, which keeps text-readability out of the injected bundle and the
32
+ * page's text out of everything: only the scores and the language ever leave
33
+ * the worker.
34
+ */
35
+ /** The one node the page-level finding points at. */
36
+ export const READING_LEVEL_RULE_ID = 'setsquare-reading-level';
37
+ /** Versioned for the ruleset digest: bump when scoring or thresholds change. */
38
+ export const READING_LEVEL_CHECK_VERSION = `${READING_LEVEL_RULE_ID}@1`;
39
+ const MIN_WORDS = 100;
40
+ const FLESCH_KINCAID_TARGET = 9;
41
+ const FLESCH_KINCAID_FIRE_ABOVE = 10;
42
+ const LIX_TARGET = 44;
43
+ const LIX_FIRE_ABOVE = 48;
44
+ /** Enough prose to grade a page; more only slows the formulas down. */
45
+ const MAX_CHARS = 20_000;
46
+ const FLESCH_LANGS = new Set(['en']);
47
+ const LIX_LANGS = new Set(['da', 'fi', 'nb', 'nn', 'no', 'sv']);
48
+ /**
49
+ * Main-content text and the document language, from the live page.
50
+ *
51
+ * `<main>`/`role=main` when the page declares one; otherwise the body minus
52
+ * nav/header/footer/aside, which is the same "grade the writing, not the
53
+ * chrome" heuristic every readability tool uses. Returns undefined on any
54
+ * failure — a page that cannot be read is a page that is not scored.
55
+ */
56
+ export async function extractMainText(page) {
57
+ try {
58
+ return await page.evaluate((maxChars) => {
59
+ const lang = document.documentElement.lang.trim().toLowerCase();
60
+ if (!lang)
61
+ return undefined;
62
+ const main = document.querySelector('main, [role="main"]');
63
+ let text;
64
+ if (main instanceof HTMLElement) {
65
+ text = main.innerText;
66
+ }
67
+ else {
68
+ const clone = document.body.cloneNode(true);
69
+ const chrome = clone.querySelectorAll('nav, header, footer, aside, script, style');
70
+ for (let i = 0; i < chrome.length; i += 1)
71
+ chrome[i]?.remove();
72
+ text = clone.innerText ?? clone.textContent ?? '';
73
+ }
74
+ return { lang, text: text.slice(0, maxChars) };
75
+ }, MAX_CHARS);
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ /**
82
+ * LIX = words/sentences + 100 × longWords/words, long = more than 6 letters.
83
+ * Computed here rather than imported: the formula is four lines, and the only
84
+ * library candidates are unmaintained.
85
+ */
86
+ function lix(text) {
87
+ const words = text.split(/\s+/).filter((word) => /\p{L}/u.test(word));
88
+ const sentences = text.split(/[.!?:;]+/).filter((part) => /\p{L}/u.test(part));
89
+ if (words.length === 0 || sentences.length === 0)
90
+ return { score: 0, words: 0 };
91
+ const longWords = words.filter((word) => word.replace(/\P{L}/gu, '').length > 6);
92
+ return {
93
+ score: words.length / sentences.length + (100 * longWords.length) / words.length,
94
+ words: words.length,
95
+ };
96
+ }
97
+ export function scoreReadingLevel(extracted) {
98
+ const primary = extracted.lang.split('-')[0] ?? '';
99
+ if (FLESCH_LANGS.has(primary)) {
100
+ const words = extracted.text.split(/\s+/).filter((word) => /\p{L}/u.test(word)).length;
101
+ if (words < MIN_WORDS)
102
+ return undefined;
103
+ const score = readability.fleschKincaidGrade(extracted.text);
104
+ if (!Number.isFinite(score))
105
+ return undefined;
106
+ return {
107
+ lang: extracted.lang,
108
+ formula: 'flesch-kincaid',
109
+ score: Math.round(score * 10) / 10,
110
+ target: FLESCH_KINCAID_TARGET,
111
+ words,
112
+ flagged: score > FLESCH_KINCAID_FIRE_ABOVE,
113
+ };
114
+ }
115
+ if (LIX_LANGS.has(primary)) {
116
+ const { score, words } = lix(extracted.text);
117
+ if (words < MIN_WORDS)
118
+ return undefined;
119
+ return {
120
+ lang: extracted.lang,
121
+ formula: 'lix',
122
+ score: Math.round(score * 10) / 10,
123
+ target: LIX_TARGET,
124
+ words,
125
+ flagged: score > LIX_FIRE_ABOVE,
126
+ };
127
+ }
128
+ // Unsupported language: silence, not a guess.
129
+ return undefined;
130
+ }
131
+ /**
132
+ * The heuristic finding, only when the score clears the margin.
133
+ *
134
+ * Page-level, so `targets` names one node — the page — with no selectors. A
135
+ * `nodeCount` of 0 would make a surface failing only this check render as
136
+ * clean, which is the false pass this product exists to avoid.
137
+ */
138
+ export function readingLevelFinding(score) {
139
+ if (!score.flagged)
140
+ return undefined;
141
+ const grade = score.formula === 'flesch-kincaid'
142
+ ? `Estimated reading grade ${score.score} (target ≤ ${score.target} after removing proper names)`
143
+ : `LIX ${score.score} (target ≤ ${score.target}; conventional mapping, not W3C-defined)`;
144
+ return {
145
+ ruleId: READING_LEVEL_RULE_ID,
146
+ status: 'incomplete',
147
+ wcag: { criteria: ['3.1.5'], level: 'AAA', versionTags: [] },
148
+ targets: { nodeCount: 1, selectors: [], selectorsTruncated: false },
149
+ helpUrl: READING_LEVEL_HELP_URL,
150
+ evidence: {
151
+ failureSummary: `${grade}. 3.1.5 can also be met with supplemental content or a simpler ` +
152
+ 'alternate version — needs a person.',
153
+ /*
154
+ * Through the same gate as axe's own data, even though every value here
155
+ * is ours. The invariant worth having is not "this rule is careful" but
156
+ * "nothing reaches `evidence.computed` without passing the contract's
157
+ * key list" — a rule that exempts itself is the hole that invariant is
158
+ * for. Today this is a no-op; the day someone adds the failing sentence
159
+ * to the payload, it stops being one.
160
+ */
161
+ computed: sanitiseComputed({
162
+ formula: score.formula,
163
+ score: score.score,
164
+ target: score.target,
165
+ words: score.words,
166
+ lang: score.lang,
167
+ }, READING_LEVEL_RULE_ID),
168
+ },
169
+ };
170
+ }
171
+ /**
172
+ * The whole check: extract, gate, score, and return a finding or nothing.
173
+ * Never throws — a readability heuristic must not be able to cost a scan.
174
+ */
175
+ export async function runReadingLevel(page) {
176
+ try {
177
+ const extracted = await extractMainText(page);
178
+ if (!extracted)
179
+ return undefined;
180
+ const score = scoreReadingLevel(extracted);
181
+ if (!score)
182
+ return undefined;
183
+ return readingLevelFinding(score);
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ }
189
+ //# sourceMappingURL=reading-level.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reading-level.js","sourceRoot":"","sources":["../src/reading-level.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult as PlaywrightTestResult } from '@playwright/test/reporter';
2
+ import { type SetsquareOptions } from './options.js';
3
+ /**
4
+ * The interpretation half of the SDK, and the only thing that exists once per
5
+ * run.
6
+ *
7
+ * Everything that is a policy lives here — envelope assembly, output, and (as
8
+ * later stages land) dedupe, precedence, surface normalisation and persona/theme
9
+ * resolution. The fixture observes in N workers and decides nothing; this decides
10
+ * everything and observes nothing. Splitting it any other way means two places
11
+ * implementing one policy, and they drift.
12
+ *
13
+ * Nothing in this file makes a network call, and nothing in this file can. That
14
+ * is not a configuration — there is no client, no URL, and no code path that
15
+ * would use one. "Zero network calls with no token" is asserted in the test suite
16
+ * against the real Node http stack, because a promise of restraint is not a
17
+ * property.
18
+ */
19
+ export default class SetsquareReporter implements Reporter {
20
+ private readonly options;
21
+ private readonly outcomes;
22
+ private readonly warnings;
23
+ private startedAt;
24
+ private baseDir;
25
+ private infrastructure;
26
+ private reportPath;
27
+ private plannedTests;
28
+ /** Set only when the orchestrator declared a run group via SETSQUARE_RUN_ID. */
29
+ private group;
30
+ private groupDir;
31
+ private partId;
32
+ private startedAtIso;
33
+ /** The begun-marker write, started in onBegin and settled before the merge. */
34
+ private begunWrite;
35
+ /** Image attachments Playwright held in memory, which nothing can link to. */
36
+ private inMemoryShots;
37
+ private unmeasuredAxes;
38
+ private projectNames;
39
+ private configuredWorkers;
40
+ private configFile;
41
+ constructor(options?: SetsquareOptions);
42
+ /** Playwright reads this to decide whether stdout should be captured. */
43
+ printsToStdio(): boolean;
44
+ /**
45
+ * `playwright test --list` enumerates tests and runs none of them.
46
+ *
47
+ * Playwright still calls `onBegin` and `onEnd` for a listing, and the reporter
48
+ * used to treat that as a run: it wrote a `run.json` with zero results, printed a
49
+ * summary, and — because `pruneRuns` replaces older run directories, by the "no
50
+ * local history" design — **DELETED the previous real run's evidence.** Measured:
51
+ * a run of 14 scans, then one `--list`, and the 14-scan directory was gone,
52
+ * replaced by an empty one. `setsquare report` then pointed at an empty report.
53
+ *
54
+ * In a run group it was worse than data loss: the listing wrote a begun marker
55
+ * and never completed, so the group's census counted a part that never existed
56
+ * and the merged coverage read `partial` forever.
57
+ *
58
+ * Detected from argv rather than from "no tests ended", because a run that was
59
+ * interrupted before its first test looks identical from the Reporter API — and
60
+ * that IS a real run whose emptiness is worth recording. What the user typed is
61
+ * the only signal that separates the two.
62
+ */
63
+ private readonly listOnly;
64
+ onBegin(config: FullConfig, suite: Suite): void;
65
+ onTestEnd(test: TestCase, result: PlaywrightTestResult): void;
66
+ onEnd(result?: FullResult): Promise<void>;
67
+ /**
68
+ * The grouped ending: write this invocation's part, then re-derive the
69
+ * merged logical run from every part present.
70
+ *
71
+ * The terminal keeps speaking for THIS invocation — a CI log per lane must
72
+ * describe its lane, and two complete-looking merged summaries in one
73
+ * pipeline log is how someone quotes the wrong one — with a group block
74
+ * naming the merged artifact and what is known about its coverage. The
75
+ * merged run.json / meta.json / report.html are the logical run.
76
+ */
77
+ private endGrouped;
78
+ /**
79
+ * The same summary again, as markdown, when someone asked for a copy.
80
+ *
81
+ * Rendered from the SAME `SummaryInput` the terminal was given, which is the
82
+ * whole point: a CI job summary that disagreed with the log above it would be
83
+ * worse than no job summary at all. `format` is not consulted — this
84
+ * destination is markdown by definition, and a suite configured for `json`
85
+ * still wants a readable panel.
86
+ *
87
+ * Silent on failure, and deliberately so. This is the last thing the reporter
88
+ * does; a path the process cannot write must not swallow the summary that was
89
+ * already printed or the evidence that was already saved.
90
+ */
91
+ private writeSummaryFile;
92
+ }
93
+ export declare function unmeasuredAxes(projects: FullConfig['projects']): string[];
94
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,UAAU,IAAI,oBAAoB,EACnC,MAAM,2BAA2B,CAAC;AAwBnC,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAY1F;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,QAAQ;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,cAAc,CAAkC;IACxD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,YAAY,CAAqB;IACzC,gFAAgF;IAChF,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,YAAY,CAAM;IAC1B,+EAA+E;IAC/E,OAAO,CAAC,UAAU,CAA4B;IAC9C,8EAA8E;IAC9E,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,UAAU,CAAqB;gBAE3B,OAAO,GAAE,gBAAqB;IAI1C,yEAAyE;IACzE,aAAa,IAAI,OAAO;IAIxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAE5D,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAqG/C,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,GAAG,IAAI;IAiDvD,KAAK,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAqJ/C;;;;;;;;;OASG;YACW,UAAU;IAiKxB;;;;;;;;;;;;OAYG;YACW,gBAAgB;CAU/B;AAwKD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,GAAG,MAAM,EAAE,CA8BzE"}