@getvitops/utils 4.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 +69 -0
- package/dist/ads/index.d.mts +228 -0
- package/dist/ads/index.d.mts.map +1 -0
- package/dist/ads/index.mjs +380 -0
- package/dist/ads/index.mjs.map +1 -0
- package/dist/cloudflare-BW-Iyi-B.mjs +92 -0
- package/dist/cloudflare-BW-Iyi-B.mjs.map +1 -0
- package/dist/cloudflare-BiK61Oq4.d.mts +27 -0
- package/dist/cloudflare-BiK61Oq4.d.mts.map +1 -0
- package/dist/notify/index.mjs +1 -1
- package/dist/onboarding/index.d.mts +1 -25
- package/dist/onboarding/index.d.mts.map +1 -1
- package/dist/onboarding/index.mjs +1 -90
- package/dist/onboarding/index.mjs.map +1 -1
- package/dist/{platform-CIdwwI_Z.mjs → platform-0nlOIy8h.mjs} +10 -2
- package/dist/platform-0nlOIy8h.mjs.map +1 -0
- package/dist/tracking/index.d.mts +6 -0
- package/dist/tracking/index.d.mts.map +1 -1
- package/dist/tracking/index.mjs +1 -1
- package/package.json +5 -1
- package/dist/platform-CIdwwI_Z.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
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
|
+
|
|
3
72
|
## 4.0.0
|
|
4
73
|
|
|
5
74
|
### Major Changes
|
|
@@ -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"}
|