@getvitops/utils 3.0.0 → 4.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,144 @@
1
1
  # @getvitops/utils
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2c890c0: Link a site to its ad properties: `site.ads`, `vitops ads`, and `<Ads />`.
8
+
9
+ `vitops search` covers the whole Search Console relationship. Nothing covered the equivalent one
10
+ with ad platforms, and a site's ad accounts had nowhere to live — so a Meta pixel pasted into a
11
+ template was invisible to the rest of the toolchain. It set `_fbp` on a site whose generated cookie
12
+ notice never mentioned it, whose consent gate never cleared it on revoke, and — for LinkedIn and
13
+ Pinterest — whose attribution never captured the click ID at all, so every conversion from those
14
+ platforms arrived indistinguishable from organic traffic.
15
+
16
+ **New `site.ads` block**, keyed by platform (`google`, `meta`, `linkedin`, `reddit`, `tiktok`,
17
+ `microsoft`, `pinterest`, `snapchat`):
18
+
19
+ ```jsonc
20
+ {
21
+ "site": {
22
+ "ads": {
23
+ "meta": { "pixelId": "123456789", "domainVerification": "abc123" },
24
+ "google": {
25
+ "accountId": "123-456-7890",
26
+ "pixelId": "AW-987654321",
27
+ "conversionLabel": "xyz",
28
+ },
29
+ },
30
+ },
31
+ }
32
+ ```
33
+
34
+ **`vitops ads setup`** ensures each platform's domain-verification DNS record. Only four platforms
35
+ verify a domain at all — Meta, TikTok, Pinterest and Snapchat, by apex DNS TXT — and that record is
36
+ the one thing created for you (in Cloudflare, via `CLOUDFLARE_API_TOKEN`; created only, never edited
37
+ or deleted). Google Ads, LinkedIn, Reddit and Microsoft Ads have no domain verification: linking
38
+ there is the tag and the account id, and the run says so rather than skipping in silence. No
39
+ platform Marketing API is called — Meta's needs a system-user token and Google's an approved
40
+ developer token with your own account on the line — so the final "Verify" click is surfaced as a
41
+ reminder. `--dry` prints the plan, `--check` reports drift and exits non-zero.
42
+
43
+ **It asks for what the config is missing.** A verification token does not exist until someone
44
+ fetches it from the platform UI, so a first run prompts for it, naming the exact UI path, folds the
45
+ answer into the plan and writes it to your config. The token is not a secret — it is published in
46
+ DNS, and the platform fetching it back is the ownership proof, exactly like the IndexNow key.
47
+ Prompting requires a TTY: with `--dry`, `--check`, `--no-prompt`, or in CI, you get a named error
48
+ instead and the run never hangs. `--no-write` keeps the answer out of the config.
49
+
50
+ **`vitops ads tags`** prints each pixel as an inert, consent-gated `<script>` — `type="text/plain"`
51
+ with the library URL on `data-src`, so an undecided visitor's page issues no third-party request.
52
+ For Bricks, WordPress, Eleventy: any stack without the Astro integration.
53
+
54
+ **`vitops ads lint`** reports the gaps that are invisible at runtime: a click ID the platform stamps
55
+ that attribution does not capture, a pixel while `site.tracking` is off, a property with no tag id.
56
+
57
+ **`<Ads />` in `@getvitops/astro`**, a sibling of `<Analytics />` rather than part of it — ad
58
+ properties come from the site config, state their own consent category (`marketing` by default,
59
+ rather than being derived), and switch per environment on their own
60
+ (`environments.<env>.ads`, defaulting to `analytics`, then true, so a preview deployment can send
61
+ pageviews without firing conversion pixels). Both components now render through one `<GatedTags />`,
62
+ so the inert markup has a single implementation.
63
+
64
+ **Your cookie notice now discloses every configured pixel** — name, cookies and opt-out — from the
65
+ same table that writes `data-consent-cookies`, so the notice and the revoke cannot disagree. If you
66
+ add `site.ads` to a config whose privacy policy or cookie notice you have already published,
67
+ re-generate and re-read them.
68
+
69
+ `li_fat_id` (LinkedIn) and `epik` (Pinterest) join the click-ID capture vocabulary; a site running
70
+ either platform starts attributing conversions it previously recorded as organic.
71
+
72
+ ## 4.0.0
73
+
74
+ ### Major Changes
75
+
76
+ - f7bc0a0: Add `vitops search setup` — onboard domains into Google Search Console as domain properties.
77
+
78
+ **Breaking:** the `vitops indexing` command has been renamed to **`vitops search notify`**. There is
79
+ no alias — update any scripts or CI that call `vitops indexing` to `vitops search notify` (same flags,
80
+ same behaviour).
81
+
82
+ **New: `vitops search setup`.** For each domain in a site config's new `site.searchConsole` block
83
+ (a record keyed by bare hostname), it:
84
+ - ensures the apex verification TXT record in Cloudflare,
85
+ - verifies ownership via the Site Verification API (DNS_TXT), retried with exponential backoff while
86
+ DNS propagates — a still-unverified domain is reported **PENDING**, not failed,
87
+ - adds the `sc-domain:` property via the Search Console API,
88
+ - adds any `delegatedOwners` to the verified Site Verification web resource.
89
+
90
+ It is idempotent (a re-run of an onboarded domain is a no-op), supports `--check` (report drift, exit
91
+ non-zero, mutate nothing) and `--dry` (print the plan, change nothing), and `--domain <name>` to
92
+ scope to one entry. DNS records are only ever created, never edited or deleted.
93
+
94
+ Credentials come from the environment, never the config: `CLOUDFLARE_API_TOKEN` (a `Zone:DNS:Edit`
95
+ token) and a Google **user OAuth refresh token** as `VITOPS_GOOGLE_CLIENT_ID` /
96
+ `VITOPS_GOOGLE_CLIENT_SECRET` / `VITOPS_GOOGLE_REFRESH_TOKEN` (scoped to `siteverification` +
97
+ `webmasters`). Granting a Google Group **Full-User** access has no Search Console API and is surfaced
98
+ as a reminder in the summary rather than automated.
99
+
100
+ - ceed51f: `vitops search notify` now accepts either Google credential, so most sites need only one.
101
+
102
+ The two halves of `vitops search` each demanded a different, unrelated Google setup — five environment variables between them. `search setup` requires a user OAuth credential (verifying a site makes the caller an **owner** of the property, and that should be a person, not a project robot), while `search notify` accepted only a service account. Search Console does not care which identity calls it, so a consumer who had already run `search setup` was being made to create a second credential for the other half of the same command.
103
+
104
+ `search notify` now takes whichever you have, preferring the service account when both are set — it runs on every deploy, in CI, and a service account does not expire, whereas a refresh token can be revoked and expires after 7 days for an OAuth client still in _Testing_ publishing status. `search setup` is unchanged and still requires user OAuth.
105
+
106
+ **Breaking — two renamed exports.** Both did the same job under the same name from different subpaths, which forced an alias at every call site:
107
+ - `@getvitops/utils/indexing`: `getAccessToken` → **`serviceAccountToken`**
108
+ - `@getvitops/utils/onboarding`: `getAccessToken` → **`refreshTokenGrant`**
109
+
110
+ New: `googleAccessToken(credential)` from `@getvitops/utils/indexing` takes a `GoogleCredential` union and is what both wrappers now call. If you were importing either `getAccessToken`, switch to the specific name, or to `googleAccessToken` if you want to accept either identity.
111
+
112
+ Internally the exchange is now one function. The two grants send different form bodies; everything after that — endpoint, content-type, error handling, `access_token` extraction — was duplicated verbatim in two modules. The service-account JWT signing had no test at all, because it was only reachable over the network; it now does, including the literal-`\n`-in-a-PEM case that secret stores produce.
113
+
114
+ ### Minor Changes
115
+
116
+ - c6b99e7: Ad-click attribution and conversion notifications ship as part of the toolchain.
117
+
118
+ A visitor arriving on an ad carries a click ID in the URL; `<Tracking />` captures it into a first-party `_ac` cookie, and when that visitor later submits a form or taps a `tel:` link, the conversion handler reads the cookie back and notifies whoever the config says. Previously this was per-site code.
119
+
120
+ **Added — `@getvitops/utils/tracking`.** The attribution vocabulary and cookie, as pure functions over a cookie _string_ so the browser capture and the server-side handler share one implementation: `parseTrackingCookie`, `serializeTrackingCookie`, `mergeTracking`, `identifyPlatform`, `getPrimaryClickId`, plus the click-ID/UTM tables. No DOM, no network, no clock — every function that needs "now" takes it as an argument, so it runs unchanged in a Worker.
121
+
122
+ **Added — `@getvitops/utils/notify`.** Conversion notifications, split the way `indexing/` is: `plan.ts` decides everything purely (which channels fire, who they reach, and **why anything is skipped**), `render.ts` turns the event into prose, `email.ts` executes and decides nothing. A misconfigured site can therefore be told exactly why no notification will arrive — a silently unsent one is indistinguishable from no conversion.
123
+
124
+ The `ConversionEvent` is the abstraction: the event is the _fact_, and how it reads is the channel's business.
125
+
126
+ **Added — the `email` channel, via Cloudflare Email Sending.** Uses the current structured binding (`env.EMAIL.send({ to, from, subject, html, text })`), not the legacy `EmailMessage` + hand-built MIME path. The binding is **passed in, never imported**, so `@getvitops/utils` takes no Cloudflare dependency and the sender is testable with a stub. Retries only the transient error codes; a configuration fault like `E_SENDER_NOT_VERIFIED` is surfaced verbatim rather than collapsed into "send failed". With no binding it prints the notification — the dev path.
127
+
128
+ Remember to onboard the sending domain (`wrangler email sending enable <domain>`) and add `"send_email": [{ "name": "EMAIL" }]` to `wrangler.jsonc`, or every send fails.
129
+
130
+ **Added — `<Tracking />` and `createConversionRoute()`** in `@getvitops/astro` (`@getvitops/astro/Tracking.astro`, `@getvitops/astro/routes`). The route factory handles both the `tel:` beacon and a form POST; validation and business rules stay the consumer's, because every site's form differs. A failed notification never fails the request — the visitor has already submitted, and refusing them because our mail didn't go out turns a lost notification into a lost conversion.
131
+
132
+ **Added — `site.tracking` and a widened `site.notifications`.** `notifications.email` now accepts a full channel object as well as a bare address (which still means what it did). `tracking` gains `category`.
133
+
134
+ **Fixed — the capture script now asks for consent instead of only reading it.** It previously checked `granted('marketing')` passively, which under the demand-driven banner is a permanent no-op: nothing else on a page demands `marketing`, so the banner never offered it, so it was never granted, so `_ac` was never written — silently, on every site with the gate enabled. It now calls `require()`, which is what raises the banner, and only when the URL actually carried a click ID or UTM. A visitor arriving organically has nothing to attribute and is never asked.
135
+
136
+ The integration adds `marketing` to the offered categories when tracking is on, so the banner has a row for the category the script will demand, and warns when tracking is enabled with `consent` off.
137
+
138
+ **Fixed — revoking consent now clears `_ac`.** The marker `<Tracking />` emits carries `data-consent-cookies`, which the consent runtime's cleanup reads. There was no cleanup path for this cookie before.
139
+
140
+ **Fixed — the cookie notice discloses `_ac`.** It is first-party, so no provider table would ever name it, and a site running attribution alongside a cookieless analytics provider was previously described as setting no cookies at all.
141
+
3
142
  ## 3.0.0
4
143
 
5
144
  ### Major Changes
package/README.md CHANGED
@@ -53,7 +53,7 @@ for why the outputs are meant to be committed.
53
53
 
54
54
  ## Search-engine notification (`@getvitops/utils/indexing`)
55
55
 
56
- The engine behind `vitops indexing`: sitemap parsing, the changed-URL diff against the previous
56
+ The engine behind `vitops search notify`: sitemap parsing, the changed-URL diff against the previous
57
57
  run's snapshot, IndexNow submission, and the Search Console API calls (sitemap resubmit + read-only
58
58
  URL inspection). `plan()` is pure — no network, no filesystem, no clock — and `indexnow.ts` /
59
59
  `gsc.ts` execute a plan without deciding anything.
@@ -0,0 +1,228 @@
1
+ import { n as findZoneId, r as listApexTxt, t as createApexTxt } from "../cloudflare-BiK61Oq4.mjs";
2
+
3
+ //#region src/ads/types.d.ts
4
+ /**
5
+ * The ads module's own option surface.
6
+ *
7
+ * Deliberately structural rather than `Config['site']['ads']` imported from
8
+ * `@getvitops/generator`: the generator already depends on this package, so
9
+ * importing back would be a cycle. The vocabulary mirrors that block field for
10
+ * field, so the CLI's adapter is a flat map — the same arrangement, and for the
11
+ * same reason, as `IndexingConfig` and `DomainSetup` beside it.
12
+ *
13
+ * Optional fields are written `?: T | undefined` throughout: `exactOptionalPropertyTypes`
14
+ * is on, and these describe a *parsed JSON config* where an absent key and an explicit
15
+ * `undefined` are the same thing.
16
+ */
17
+ /**
18
+ * The ad platforms this toolchain knows how to link a site to.
19
+ *
20
+ * A closed set, mirroring `AD_PROVIDERS` in the generator's `config.ts` — the
21
+ * capability table in `providers.ts` has one entry per member, and a test in the
22
+ * generator asserts the two lists agree. An open string would mean a typo'd
23
+ * provider silently getting no verification step and no tag.
24
+ */
25
+ type AdProvider = 'google' | 'meta' | 'linkedin' | 'reddit' | 'tiktok' | 'microsoft' | 'pinterest' | 'snapchat';
26
+ /** Which consent category gates a provider's tag. */
27
+ type AdConsentCategory = 'marketing' | 'analytics';
28
+ /** One ad property to link, as the config declares it. */
29
+ interface AdPropertySetup {
30
+ provider: AdProvider;
31
+ /**
32
+ * The account as the platform shows it — Google Ads customer id, Meta ad
33
+ * account, LinkedIn partner id, Reddit advertiser id.
34
+ */
35
+ accountId?: string | undefined;
36
+ /** The tag/pixel id, when the platform issues one distinct from the account. */
37
+ pixelId?: string | undefined;
38
+ /** Google Ads only: the conversion label paired with the conversion id. */
39
+ conversionLabel?: string | undefined;
40
+ /** The host to verify, bare (`acme.ca`). The CLI defaults it from the canonical domain. */
41
+ domain?: string | undefined;
42
+ /**
43
+ * The verification token from the platform UI.
44
+ *
45
+ * A bare token gets the provider's TXT prefix applied; a value already
46
+ * containing `=` is used as the whole record content, which is the escape hatch
47
+ * for a platform changing its prefix without waiting on a toolchain release.
48
+ */
49
+ domainVerification?: string | undefined;
50
+ /** Category the tag waits on. Defaults to `marketing`. */
51
+ category?: AdConsentCategory | undefined;
52
+ enabled?: boolean | undefined;
53
+ }
54
+ /** Everything `plan()` needs, once the CLI has read the config. */
55
+ interface AdsConfig {
56
+ /** The properties to link. Already filtered to `--provider` if that was passed. */
57
+ properties: AdPropertySetup[];
58
+ }
59
+ /**
60
+ * One property's observed live state, gathered by the executor before planning.
61
+ *
62
+ * Same shape of arrangement as `DomainState`: the decision is pure, the
63
+ * observation is not. `txtContents` is every apex TXT record on the domain, so
64
+ * the planner can decide both "our record is present" and "a *different* record
65
+ * from this provider is present" — the second is a token rotation, which needs
66
+ * saying rather than silently adding a second record.
67
+ */
68
+ interface AdDomainState {
69
+ /** The Cloudflare zone id for the domain, if one was found. */
70
+ zoneId?: string | undefined;
71
+ /** Every apex TXT record content, unquoted. */
72
+ txtContents: string[];
73
+ }
74
+ //#endregion
75
+ //#region src/ads/plan.d.ts
76
+ /** A step's decision. `blocked` means the config is missing something. */
77
+ type StepAction = 'create' | 'skip' | 'blocked';
78
+ /** A config field the operator can supply to unblock a step. */
79
+ type MissingField = 'domainVerification' | 'accountId' | 'pixelId';
80
+ interface StepPlan {
81
+ action: StepAction;
82
+ /** Why it's a skip, or what the mutation will do, or what is missing. */
83
+ detail: string;
84
+ /** Set only on `blocked`: which config field would unblock it. */
85
+ needs?: MissingField | undefined;
86
+ }
87
+ /** The steps for one ad property, decided against its live state. */
88
+ interface AdPropertyPlan {
89
+ provider: AdPropertySetup['provider'];
90
+ /** The platform name, so a formatter needn't reach back into the table. */
91
+ name: string;
92
+ /** The domain being verified, when the platform verifies one. */
93
+ domain?: string | undefined;
94
+ /** Ensure the apex verification TXT. */
95
+ txt: StepPlan;
96
+ /** Whether the tag can be emitted (`ads tags`). */
97
+ tag: StepPlan;
98
+ /** The exact TXT content to create, when `txt.action` is `create`. */
99
+ txtContent?: string | undefined;
100
+ /** Manual follow-ups — including *why* a platform has no verification step. */
101
+ reminders: string[];
102
+ }
103
+ interface AdsPlan {
104
+ properties: AdPropertyPlan[];
105
+ /** Operator-facing warnings that span the whole run. */
106
+ notes: string[];
107
+ }
108
+ /**
109
+ * Decide one property's steps.
110
+ *
111
+ * The `dns-txt` branch has three outcomes, and keeping them distinct is the point:
112
+ * our record is present (skip), a *different* record from the same platform is
113
+ * present (create, and say so — a token rotation leaves the old record in place,
114
+ * since this command never edits or deletes DNS), or no token is configured yet
115
+ * (blocked, naming the field).
116
+ */
117
+ declare function planProperty(setup: AdPropertySetup, state: AdDomainState): AdPropertyPlan;
118
+ /** Plan every configured property. */
119
+ declare function plan(config: AdsConfig, states: Map<string, AdDomainState>): AdsPlan;
120
+ /** Any step that isn't a skip — i.e. the live state or the config has drifted. */
121
+ declare function hasDrift(p: AdsPlan): boolean;
122
+ /** Every field a run still needs, per provider — what the CLI asks for. */
123
+ declare function missingFields(p: AdsPlan): {
124
+ provider: string;
125
+ needs: MissingField;
126
+ }[];
127
+ /** Render a plan as the block `--dry` and `--check` print. */
128
+ declare function formatPlan(p: AdsPlan): string;
129
+ /** The outcome of one property's run, for the summary table. */
130
+ interface AdPropertyResult {
131
+ provider: string;
132
+ domain: string;
133
+ txt: 'created' | 'present' | 'blocked' | 'failed' | 'n/a';
134
+ tag: 'ready' | 'blocked';
135
+ reminders: string[];
136
+ }
137
+ /** Render the end-of-run summary: one row per property, plus any reminders. */
138
+ declare function formatSummary(results: AdPropertyResult[]): string;
139
+ //#endregion
140
+ //#region src/ads/providers.d.ts
141
+ /** How a platform proves you own the domain. */
142
+ interface AdVerificationSpec {
143
+ method: 'dns-txt' | 'none';
144
+ /**
145
+ * Prefix prepended to a bare token to form the record content. Owned here so a
146
+ * config holds the token the platform shows, not a hand-assembled record — but
147
+ * see `txtRecord`: a value already containing `=` passes through whole, which is
148
+ * the escape hatch when a platform changes its spelling.
149
+ */
150
+ txtPrefix?: string;
151
+ /** Where in the platform UI the token is found. Printed when asking for it. */
152
+ where?: string;
153
+ /** Why there is nothing to automate. Required when `method` is `none`. */
154
+ reason?: string;
155
+ }
156
+ /** The tag, and which config field carries its id. */
157
+ interface AdTagSpec {
158
+ /** Which field the tag id comes from — and therefore what to ask for when absent. */
159
+ needs: 'pixelId' | 'accountId';
160
+ /** The remote library. Absent when the platform's own bootstrap loads itself. */
161
+ src?: (id: string) => string;
162
+ /** The inline bootstrap, which the gate runs before attaching `src`. */
163
+ bootstrap: (id: string, entry: AdPropertySetup) => string;
164
+ /**
165
+ * Cookies the tag sets, for `data-consent-cookies` (what a revoke clears) and
166
+ * for the generated cookie notice. A `*` suffix is a prefix match.
167
+ */
168
+ cookies: string[];
169
+ /** The provider's own privacy policy, for the cookie notice. */
170
+ privacyUrl: string;
171
+ /** A visitor-facing opt-out, where the platform offers one. */
172
+ optOut?: string;
173
+ }
174
+ interface AdPlatform {
175
+ /** The platform as a reader would recognise it. */
176
+ name: string;
177
+ /**
178
+ * The jurisdiction that can compel this platform to produce what it receives —
179
+ * where the contracting entity is established. Feeds the generated privacy
180
+ * policy's cross-border transfer disclosure, which is a claim about foreign
181
+ * *access*, not foreign storage (see `Processor.operatorCountry` in the
182
+ * generator's `legal/providers.ts`). A consumer whose contracting entity differs
183
+ * declares their own processor entry instead.
184
+ */
185
+ operatorCountry: string;
186
+ /** Click-ID parameters this platform stamps on an ad landing URL. */
187
+ clickIdParams: string[];
188
+ /** Where the operator manages the property. */
189
+ consoleUrl: string;
190
+ verification: AdVerificationSpec;
191
+ tag: AdTagSpec;
192
+ /** Follow-ups no API covers, given this entry. */
193
+ manualSteps: (entry: AdPropertySetup) => string[];
194
+ }
195
+ declare const AD_PLATFORMS: Record<AdProvider, AdPlatform>;
196
+ /** Every provider, in table order — the one place iteration order is decided. */
197
+ declare const AD_PROVIDER_KEYS: AdProvider[];
198
+ /** Whether a string names a known provider (for CLI arg validation). */
199
+ declare const isAdProvider: (value: string) => value is AdProvider;
200
+ /**
201
+ * The DNS TXT record content for a token.
202
+ *
203
+ * A value already containing `=` is taken as the whole record and left alone. That
204
+ * is the same escape hatch an icon name containing `:` gets: a platform can change
205
+ * its prefix spelling without waiting on a toolchain release, and an operator who
206
+ * pasted the full record from the UI gets what they meant rather than a
207
+ * double-prefixed record that verifies against nothing.
208
+ */
209
+ declare function txtRecord(provider: AdProvider, token: string): string;
210
+ /** The tag id for an entry, or undefined when the field it needs is unset. */
211
+ declare function tagId(entry: AdPropertySetup): string | undefined;
212
+ /** The consent category gating an entry's tag. `marketing` unless stated otherwise. */
213
+ declare const categoryOf: (entry: AdPropertySetup) => AdConsentCategory;
214
+ /**
215
+ * One provider's tag, rendered as a **consent-gated, inert** `<script>`.
216
+ *
217
+ * `type="text/plain"` with the URL on `data-src` is what makes the gate real: the
218
+ * browser neither parses the body nor fetches the library, so an undecided
219
+ * visitor's page issues no third-party request. Never give one of these a live
220
+ * `src` — a gate that instead asks a loaded tracker not to track is a promise.
221
+ *
222
+ * Returns null when the entry has no tag id, so a caller can report the gap rather
223
+ * than emit a snippet that initialises `undefined`.
224
+ */
225
+ declare function renderTag(entry: AdPropertySetup, strategy?: 'idle' | 'async' | 'interaction'): string | null;
226
+ //#endregion
227
+ export { AD_PLATFORMS, AD_PROVIDER_KEYS, type AdConsentCategory, type AdDomainState, type AdPlatform, type AdPropertyPlan, type AdPropertyResult, type AdPropertySetup, type AdProvider, type AdTagSpec, type AdVerificationSpec, type AdsConfig, type AdsPlan, type MissingField, type StepAction, type StepPlan, categoryOf, createApexTxt, findZoneId, formatPlan, formatSummary, hasDrift, isAdProvider, listApexTxt, missingFields, plan, planProperty, renderTag, tagId, txtRecord };
228
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/ads/types.ts","../../src/ads/plan.ts","../../src/ads/providers.ts"],"mappings":";;;;;;AAsBA;;;;AAAsB;AAWtB;;;;AAA6B;AAG7B;;;;;;;;AAAA,KAdY,UAAA;;KAWA,iBAAA;;UAGK,eAAA;EACf,QAAA,EAAU,UAAA;EAsBH;AAAA;AAIT;;EArBE,SAAA;EAuBA;EArBA,OAAA;EAiCe;EA/Bf,eAAA;;EAEA,MAAA;EAiCW;;;;ACzDb;;;EDgCE,kBAAA;EChCoB;EDkCpB,QAAA,GAAW,iBAAiB;EAC5B,OAAA;AAAA;;UAIe,SAAA;EClCA;EDoCf,UAAA,EAAY,eAAe;AAAA;;;;;;;;AC/BP;AAItB;UDuCiB,aAAA;;EAEf,MAAA;EClCK;EDoCL,WAAW;AAAA;;;;KCzDD,UAAA;ADFZ;AAAA,KCKY,YAAA;AAAA,UAEK,QAAA;EACf,MAAA,EAAQ,UAAA;EDRY;ECUpB,MAAA;EDC2B;ECC3B,KAAA,GAAQ,YAAY;AAAA;ADDO;AAAA,UCKZ,cAAA;EACf,QAAA,EAAU,eAAA;;EAEV,IAAA;EDJA;ECMA,MAAA;EDDA;ECGA,GAAA,EAAK,QAAA;EDCL;ECCA,GAAA,EAAK,QAAA;EDSL;ECPA,UAAA;EDSW;ECPX,SAAA;AAAA;AAAA,UAGe,OAAA;EACf,UAAA,EAAY,cAAc;EDQF;ECNxB,KAAA;AAAA;ADQ2B;AAY7B;;;;AAIa;;;;AAhBgB,iBCIb,YAAA,CAAa,KAAA,EAAO,eAAA,EAAiB,KAAA,EAAO,aAAA,GAAgB,cAAA;;iBAoD5D,IAAA,CAAK,MAAA,EAAQ,SAAA,EAAW,MAAA,EAAQ,GAAA,SAAY,aAAA,IAAiB,OAAA;;iBAe7D,QAAA,CAAS,CAAU,EAAP,OAAO;AA7GnC;AAAA,iBAkHgB,aAAA,CAAc,CAAA,EAAG,OAAA;EAAY,QAAA;EAAkB,KAAA,EAAO,YAAY;AAAA;AAhHlF;AAAA,iBA4HgB,UAAA,CAAW,CAAU,EAAP,OAAO;;UAcpB,gBAAA;EACf,QAAA;EACA,MAAA;EACA,GAAA;EACA,GAAA;EACA,SAAA;AAAA;AA1IoB;AAAA,iBAgJN,aAAA,CAAc,OAA2B,EAAlB,gBAAgB;;;ADjH1B;AAAA,UE9BZ,kBAAA;EACf,MAAA;;;AF6CW;;;;EEtCX,SAAA;EDnBoB;ECqBpB,KAAA;EDrBoB;ECuBpB,MAAA;AAAA;;UAIe,SAAA;EDxBO;EC0BtB,KAAA;EDxBe;EC0Bf,GAAA,IAAO,EAAA;;EAEP,SAAA,GAAY,EAAA,UAAY,KAAA,EAAO,eAAe;ED3B9C;;;;ECgCA,OAAA;ED5BoB;EC8BpB,UAAA;ED1Be;EC4Bf,MAAA;AAAA;AAAA,UAGe,UAAA;EDxBV;EC0BL,IAAA;EDxBa;;;;;;;;ECiCb,eAAA;EDjCK;ECmCL,aAAA;ED/BA;ECiCA,UAAA;EACA,YAAA,EAAc,kBAAA;EACd,GAAA,EAAK,SAAA;EDhCiB;ECkCtB,WAAA,GAAc,KAAA,EAAO,eAAA;AAAA;AAAA,cAaV,YAAA,EAAc,MAAA,CAAO,UAAA,EAAY,UAAA;;cAuMjC,gBAAA,EAAgD,UAAU;;cAG1D,YAAA,GAAgB,KAAA,aAAgB,KAAA,IAAS,UAAmC;AD1OzF;;;;;;;;;AAAA,iBCqPgB,SAAA,CAAU,QAAA,EAAU,UAAU,EAAE,KAAA;;iBAOhC,KAAA,CAAM,KAAsB,EAAf,eAAe;;cAM/B,UAAA,GAAc,KAAA,EAAO,eAAA,KAAkB,iBACrB;ADnQ2D;AAoD1F;;;;;;;;;;AApD0F,iBCmR1E,SAAA,CACd,KAAA,EAAO,eAAe,EACtB,QAAA"}