@codaco/analytics 8.0.0 → 9.0.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/README.md CHANGED
@@ -1,9 +1,491 @@
1
1
  # @codaco/analytics
2
2
 
3
- This npm package implements methods and types for sending analytics and errors from Fresco instances to a custom error and analytics microservice.
3
+ PostHog analytics wrapper for Network Canvas applications with installation ID tracking, error reporting, and feature flags support.
4
4
 
5
- It exports two functions:
5
+ ## Overview
6
6
 
7
- **createRouteHandler** - A function that creates a NextJs route handler which geolocates requests, and forwards the event payload to the microservice.
7
+ This package provides a simplified abstraction over PostHog analytics, designed specifically for Network Canvas applications. It ensures that every analytics event includes an installation ID, making it possible to track deployments without compromising user privacy.
8
8
 
9
- **makeEventTracker** - A function that returns a `trackEvent` function, which attaches timestamp data to an event, and then calls the route handler.
9
+ **Important:** This package is designed to work exclusively with the Cloudflare Worker reverse proxy at `ph-relay.networkcanvas.com`. All PostHog authentication is handled by the worker, so you don't need to configure API keys in your application.
10
+
11
+ ## Features
12
+
13
+ - **Installation ID Tracking**: Automatically includes installation ID with every event
14
+ - **Error Tracking**: Capture errors with full stack traces
15
+ - **Feature Flags**: Built-in support for PostHog feature flags and A/B testing
16
+ - **Non-blocking API**: All tracking calls are fire-and-forget
17
+ - **Type Safety**: Full TypeScript support with Zod schemas
18
+ - **Server & Client**: Works in React components, server actions, and API routes
19
+ - **Privacy-First**: Analytics can be completely disabled via environment variable
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pnpm add @codaco/analytics
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ **Minimal environment variables!** Only `DISABLE_ANALYTICS` is supported for disabling tracking. All other configuration is passed directly to the analytics provider.
30
+
31
+ ### Environment Variables (Optional)
32
+
33
+ ```bash
34
+ # Optional: Disable all analytics tracking
35
+ DISABLE_ANALYTICS=true
36
+ # or for Next.js client-side
37
+ NEXT_PUBLIC_DISABLE_ANALYTICS=true
38
+ ```
39
+
40
+ ### Client-Side Usage (React)
41
+
42
+ ```tsx
43
+ // In your root layout (app/layout.tsx)
44
+ import { AnalyticsProvider } from '@codaco/analytics';
45
+
46
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
47
+ return (
48
+ <html>
49
+ <body>
50
+ <AnalyticsProvider
51
+ config={{
52
+ installationId: 'your-unique-installation-id',
53
+ }}
54
+ >
55
+ {children}
56
+ </AnalyticsProvider>
57
+ </body>
58
+ </html>
59
+ );
60
+ }
61
+ ```
62
+
63
+ ```tsx
64
+ // In your components
65
+ import { useAnalytics } from '@codaco/analytics';
66
+
67
+ export function MyComponent() {
68
+ const { trackEvent, trackError } = useAnalytics();
69
+
70
+ const handleSetup = () => {
71
+ trackEvent('app_setup', {
72
+ metadata: { version: '1.0.0' }
73
+ });
74
+ };
75
+
76
+ const handleError = (error: Error) => {
77
+ trackError(error, {
78
+ metadata: { context: 'component-mount' }
79
+ });
80
+ };
81
+
82
+ return <button onClick={handleSetup}>Setup App</button>;
83
+ }
84
+ ```
85
+
86
+ ### Server-Side Usage
87
+
88
+ ```ts
89
+ // First, initialize in your root layout or middleware
90
+ import { initServerAnalytics } from '@codaco/analytics/server';
91
+
92
+ initServerAnalytics({
93
+ installationId: 'your-unique-installation-id',
94
+ });
95
+
96
+ // Then use in API routes or server actions
97
+ import { serverAnalytics } from '@codaco/analytics/server';
98
+
99
+ export async function POST(request: Request) {
100
+ try {
101
+ // Your logic here
102
+
103
+ serverAnalytics.trackEvent('data_exported', {
104
+ metadata: { format: 'csv', rowCount: 100 }
105
+ });
106
+
107
+ return Response.json({ success: true });
108
+ } catch (error) {
109
+ serverAnalytics.trackError(error as Error);
110
+ return Response.json({ error: 'Failed' }, { status: 500 });
111
+ }
112
+ }
113
+ ```
114
+
115
+ ## API Reference
116
+
117
+ ### Event Types
118
+
119
+ The package supports the following standard event types:
120
+
121
+ - `app_setup` - Initial application setup
122
+ - `protocol_installed` - Protocol installation
123
+ - `interview_started` - Interview start
124
+ - `interview_completed` - Interview completion
125
+ - `data_exported` - Data export
126
+ - `error` - Error events (use `trackError()` instead)
127
+
128
+ You can also send custom events with any string name.
129
+
130
+ ### Client-Side API
131
+
132
+ #### `AnalyticsProvider`
133
+
134
+ Wraps your application to provide analytics context.
135
+
136
+ ```tsx
137
+ <AnalyticsProvider config={config}>
138
+ {children}
139
+ </AnalyticsProvider>
140
+ ```
141
+
142
+ **Config Options:**
143
+
144
+ ```typescript
145
+ interface AnalyticsConfig {
146
+ // Required: Unique identifier for this installation
147
+ installationId: string;
148
+
149
+ // Optional: PostHog API key
150
+ // Defaults to a placeholder (authentication handled by the proxy)
151
+ apiKey?: string;
152
+
153
+ // Optional: PostHog API host
154
+ // Hardcoded to "https://ph-relay.networkcanvas.com" by default
155
+ // Only override if using a different proxy endpoint
156
+ apiHost?: string;
157
+
158
+ // Optional: Disable all tracking
159
+ disabled?: boolean;
160
+
161
+ // Optional: Enable debug mode
162
+ debug?: boolean;
163
+
164
+ // Optional: PostHog-specific options
165
+ posthogOptions?: {
166
+ disable_session_recording?: boolean;
167
+ autocapture?: boolean;
168
+ capture_pageview?: boolean;
169
+ // ... other PostHog options
170
+ };
171
+ }
172
+ ```
173
+
174
+ #### `useAnalytics()`
175
+
176
+ React hook to access analytics in components.
177
+
178
+ ```tsx
179
+ const analytics = useAnalytics();
180
+
181
+ // Track an event
182
+ analytics.trackEvent('protocol_installed', {
183
+ metadata: { protocolId: '123', version: '2.0' }
184
+ });
185
+
186
+ // Track an error
187
+ analytics.trackError(error, {
188
+ metadata: { context: 'import' }
189
+ });
190
+
191
+ // Feature flags
192
+ const isEnabled = analytics.isFeatureEnabled('new-feature');
193
+ const variant = analytics.getFeatureFlag('experiment');
194
+ const allFlags = analytics.getFeatureFlags();
195
+ analytics.reloadFeatureFlags();
196
+
197
+ // User identification (optional, for advanced use cases)
198
+ analytics.identify('user-123', { email: 'user@example.com' });
199
+ analytics.reset();
200
+
201
+ // Utilities
202
+ analytics.isEnabled(); // Check if analytics is enabled
203
+ analytics.getInstallationId(); // Get the installation ID
204
+ ```
205
+
206
+ #### `useFeatureFlag(flagKey: string)`
207
+
208
+ React hook for feature flags (returns boolean).
209
+
210
+ ```tsx
211
+ const isNewUIEnabled = useFeatureFlag('new-ui');
212
+
213
+ if (isNewUIEnabled) {
214
+ return <NewUI />;
215
+ }
216
+
217
+ return <OldUI />;
218
+ ```
219
+
220
+ #### `useFeatureFlagValue(flagKey: string)`
221
+
222
+ React hook for multivariate feature flags.
223
+
224
+ ```tsx
225
+ const theme = useFeatureFlagValue('theme-experiment');
226
+
227
+ return <div className={theme === 'dark' ? 'dark' : 'light'}>
228
+ Content
229
+ </div>;
230
+ ```
231
+
232
+ ### Server-Side API
233
+
234
+ #### `serverAnalytics`
235
+
236
+ Pre-configured server-side analytics instance (auto-initializes from env vars).
237
+
238
+ ```ts
239
+ import { serverAnalytics } from '@codaco/analytics/server';
240
+
241
+ serverAnalytics.trackEvent('interview_completed', {
242
+ metadata: { duration: 300, completionRate: 0.95 }
243
+ });
244
+
245
+ serverAnalytics.trackError(error);
246
+ ```
247
+
248
+ #### `initServerAnalytics(config)`
249
+
250
+ Initialize server analytics (required before using serverAnalytics).
251
+
252
+ ```ts
253
+ import { initServerAnalytics, getServerAnalytics } from '@codaco/analytics/server';
254
+
255
+ // In your app startup (e.g., root layout)
256
+ initServerAnalytics({
257
+ installationId: 'your-unique-installation-id',
258
+ // Optional overrides:
259
+ // disabled: false,
260
+ // debug: true,
261
+ });
262
+
263
+ // Later, in your code
264
+ const analytics = getServerAnalytics();
265
+ analytics.trackEvent('app_setup');
266
+ ```
267
+
268
+ **Note:** Feature flags are **not supported** in server-side mode. Use client-side hooks for feature flags.
269
+
270
+ ## Configuration
271
+
272
+ ### Default Settings
273
+
274
+ The package comes with sensible defaults (no environment variables needed):
275
+
276
+ ```typescript
277
+ {
278
+ apiHost: "https://ph-relay.networkcanvas.com", // Hardcoded
279
+ apiKey: "phc_proxy_mode_placeholder", // Placeholder for proxy mode
280
+ disabled: false,
281
+ debug: false,
282
+ posthogOptions: {
283
+ disable_session_recording: true,
284
+ autocapture: false,
285
+ capture_pageview: false,
286
+ capture_pageleave: false,
287
+ cross_subdomain_cookie: false,
288
+ advanced_disable_feature_flags: false,
289
+ persistence: "localStorage+cookie",
290
+ }
291
+ }
292
+ ```
293
+
294
+ ### Configuration
295
+
296
+ All configuration is passed directly via the `config` prop.
297
+
298
+ **Environment Variables:**
299
+ - `DISABLE_ANALYTICS` or `NEXT_PUBLIC_DISABLE_ANALYTICS` - Set to `"true"` to disable all tracking
300
+
301
+ No other environment variables are used (API host and key are hardcoded for proxy mode).
302
+
303
+ ### Disabling Analytics
304
+
305
+ Analytics can be disabled in two ways:
306
+
307
+ **1. Via environment variable (recommended for development/testing):**
308
+ ```bash
309
+ DISABLE_ANALYTICS=true
310
+ # or for Next.js client-side
311
+ NEXT_PUBLIC_DISABLE_ANALYTICS=true
312
+ ```
313
+
314
+ **2. Via config prop:**
315
+ ```tsx
316
+ <AnalyticsProvider config={{
317
+ installationId: 'your-id',
318
+ disabled: true
319
+ }}>
320
+ {children}
321
+ </AnalyticsProvider>
322
+ ```
323
+
324
+ When disabled (by either method), all analytics methods become no-ops (no tracking occurs).
325
+
326
+ ## Feature Flags & A/B Testing
327
+
328
+ PostHog feature flags allow you to:
329
+
330
+ - Toggle features on/off remotely
331
+ - Run A/B tests
332
+ - Gradual rollouts
333
+ - User targeting
334
+
335
+ ### Setting Up Feature Flags
336
+
337
+ 1. Create a feature flag in your PostHog dashboard
338
+ 2. Use the hooks or API in your code:
339
+
340
+ ```tsx
341
+ function ExperimentComponent() {
342
+ const variant = useFeatureFlagValue('checkout-flow');
343
+
344
+ switch (variant) {
345
+ case 'variant-a':
346
+ return <CheckoutFlowA />;
347
+ case 'variant-b':
348
+ return <CheckoutFlowB />;
349
+ default:
350
+ return <CheckoutFlowDefault />;
351
+ }
352
+ }
353
+ ```
354
+
355
+ ## Error Tracking
356
+
357
+ Errors are automatically enriched with:
358
+
359
+ - Error message
360
+ - Error name
361
+ - Full stack trace
362
+ - Cause (if available)
363
+ - Any additional metadata you provide
364
+
365
+ ```tsx
366
+ try {
367
+ // Your code
368
+ } catch (error) {
369
+ trackError(error as Error, {
370
+ metadata: {
371
+ context: 'data-import',
372
+ fileName: 'protocol.json',
373
+ attemptNumber: 3,
374
+ }
375
+ });
376
+ }
377
+ ```
378
+
379
+ ## Migration from Old Analytics
380
+
381
+ See [MIGRATION.md](./MIGRATION.md) for a complete migration guide from the old analytics system.
382
+
383
+ Quick summary of changes:
384
+
385
+ - **Event names**: Now use `snake_case` instead of `PascalCase`
386
+ - `AppSetup` → `app_setup`
387
+ - `ProtocolInstalled` → `protocol_installed`
388
+ - **API**: `makeEventTracker()` → `useAnalytics()` hook
389
+ - **Route handler**: No longer needed (PostHog handles everything)
390
+ - **Installation ID**: Now set via config instead of route handler
391
+
392
+ ## Architecture
393
+
394
+ This package uses a reverse proxy architecture for security and reliability:
395
+
396
+ ```
397
+ ┌─────────────────────┐
398
+ │ React App │
399
+ │ (Client) │
400
+ │ │
401
+ │ useAnalytics() │
402
+ │ ↓ │
403
+ │ PostHog JS SDK │
404
+ │ (no API key) │
405
+ └──────────┬──────────┘
406
+
407
+ │ HTTPS
408
+
409
+ ┌─────────────────────┐
410
+ │ Cloudflare Worker │
411
+ │ (Reverse Proxy) │
412
+ │ ph-relay.network │
413
+ │ canvas.com │
414
+ │ │
415
+ │ • Injects API key │
416
+ │ • Adds CORS │
417
+ │ • Bypasses blockers│
418
+ └──────────┬──────────┘
419
+
420
+ │ HTTPS + Auth
421
+
422
+ ┌─────────────────────┐
423
+ │ PostHog Cloud │
424
+ │ (US Region) │
425
+ │ │
426
+ │ Analytics Dashboard│
427
+ │ Feature Flags │
428
+ │ A/B Testing │
429
+ └─────────────────────┘
430
+ ```
431
+
432
+ **Benefits:**
433
+ - API key stays secure (server-side only)
434
+ - Avoids ad-blockers
435
+ - Uses your own domain
436
+ - Centralized authentication
437
+
438
+ ## Best Practices
439
+
440
+ 1. **Always include metadata**: Helps with debugging and analysis
441
+ ```ts
442
+ trackEvent('data_exported', {
443
+ metadata: { format: 'csv', size: 1024, duration: 230 }
444
+ });
445
+ ```
446
+
447
+ 2. **Use typed event names**: Import event types for autocomplete
448
+ ```ts
449
+ import type { EventType } from '@codaco/analytics';
450
+ const event: EventType = 'app_setup';
451
+ ```
452
+
453
+ 3. **Don't block on analytics**: All calls are non-blocking by design
454
+
455
+ 4. **Test with analytics disabled**: Set `NEXT_PUBLIC_DISABLE_ANALYTICS=true`
456
+
457
+ 5. **Use feature flags for gradual rollouts**: Test features with a small percentage of users first
458
+
459
+ ## Troubleshooting
460
+
461
+ ### Events not appearing in PostHog
462
+
463
+ 1. Verify the reverse proxy is running at `ph-relay.networkcanvas.com`
464
+ 2. Check that the Cloudflare Worker has the `POSTHOG_API_KEY` environment variable set
465
+ 3. Enable debug mode: `debug: true` in config
466
+ 4. Check browser console for errors
467
+ 5. Verify PostHog dashboard shows your project
468
+ 6. Test the proxy endpoint directly: `curl https://ph-relay.networkcanvas.com`
469
+
470
+ ### Feature flags not working
471
+
472
+ 1. Ensure flags are created in PostHog dashboard
473
+ 2. Check flag is enabled and has a rollout percentage
474
+ 3. Reload flags: `analytics.reloadFeatureFlags()`
475
+ 4. Feature flags only work client-side, not in server components
476
+
477
+ ### TypeScript errors
478
+
479
+ 1. Ensure `@types/react` is installed
480
+ 2. Check TypeScript version is compatible (>= 5.0)
481
+ 3. Rebuild the package: `pnpm build`
482
+
483
+ ## License
484
+
485
+ MIT
486
+
487
+ ## Support
488
+
489
+ For issues and questions:
490
+ - GitHub Issues: https://github.com/complexdatacollective/network-canvas-monorepo/issues
491
+ - Email: hello@complexdatacollective.org
@@ -0,0 +1,72 @@
1
+ // src/config.ts
2
+ var POSTHOG_PROXY_HOST = "https://ph-relay.networkcanvas.com";
3
+ var PROXY_MODE_DUMMY_KEY = "phc_proxy_mode_placeholder";
4
+ function isDisabledByEnv() {
5
+ if (typeof process === "undefined") {
6
+ return false;
7
+ }
8
+ return process.env.DISABLE_ANALYTICS === "true" || process.env.NEXT_PUBLIC_DISABLE_ANALYTICS === "true";
9
+ }
10
+ var defaultConfig = {
11
+ // Always use the Cloudflare Worker reverse proxy
12
+ apiHost: POSTHOG_PROXY_HOST,
13
+ // Analytics enabled by default (can be disabled via env var or config option)
14
+ disabled: false,
15
+ // Debug mode disabled by default
16
+ debug: false,
17
+ // Default PostHog options
18
+ posthogOptions: {
19
+ // Disable session recording by default (can be enabled per-app)
20
+ disable_session_recording: true,
21
+ // Disable autocapture to keep events clean and intentional
22
+ autocapture: false,
23
+ // Disable automatic pageview capture (apps can enable if needed)
24
+ capture_pageview: false,
25
+ // Disable pageleave events
26
+ capture_pageleave: false,
27
+ // Don't use cross-subdomain cookies
28
+ cross_subdomain_cookie: false,
29
+ // Enable feature flags by default
30
+ advanced_disable_feature_flags: false,
31
+ // Send feature flag events
32
+ advanced_disable_feature_flags_on_first_load: false,
33
+ // Enable persistence for feature flags
34
+ persistence: "localStorage+cookie"
35
+ }
36
+ };
37
+ function mergeConfig(userConfig) {
38
+ return {
39
+ apiHost: userConfig.apiHost ?? defaultConfig.apiHost ?? POSTHOG_PROXY_HOST,
40
+ apiKey: userConfig.apiKey ?? PROXY_MODE_DUMMY_KEY,
41
+ installationId: userConfig.installationId,
42
+ disabled: userConfig.disabled ?? isDisabledByEnv() ?? defaultConfig.disabled ?? false,
43
+ debug: userConfig.debug ?? defaultConfig.debug ?? false,
44
+ posthogOptions: {
45
+ ...defaultConfig.posthogOptions,
46
+ ...userConfig.posthogOptions
47
+ }
48
+ };
49
+ }
50
+
51
+ // src/utils.ts
52
+ function ensureError(value) {
53
+ if (!value) return new Error("No value was thrown");
54
+ if (value instanceof Error) return value;
55
+ if (Object.prototype.isPrototypeOf.call(value, Error)) return value;
56
+ let stringified = "[Unable to stringify the thrown value]";
57
+ try {
58
+ stringified = JSON.stringify(value);
59
+ } catch (e) {
60
+ console.error(e);
61
+ }
62
+ const error = new Error(`This value was thrown as is, not through an Error: ${stringified}`);
63
+ return error;
64
+ }
65
+
66
+ export {
67
+ isDisabledByEnv,
68
+ defaultConfig,
69
+ mergeConfig,
70
+ ensureError
71
+ };
72
+ //# sourceMappingURL=chunk-3NEQVIC4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/utils.ts"],"sourcesContent":["import type { AnalyticsConfig } from \"./types\";\n\n/**\n * Hardcoded PostHog API host - always uses the Cloudflare Worker reverse proxy\n * Authentication is handled by the worker at this endpoint\n */\nconst POSTHOG_PROXY_HOST = \"https://ph-relay.networkcanvas.com\";\n\n/**\n * Dummy API key used for proxy mode\n * PostHog JS library requires an API key for initialization, but when using\n * the reverse proxy, authentication is handled by the Cloudflare Worker.\n * This placeholder key is used for client-side initialization only.\n */\nconst PROXY_MODE_DUMMY_KEY = \"phc_proxy_mode_placeholder\";\n\n/**\n * Check if analytics is disabled via environment variables\n */\nexport function isDisabledByEnv(): boolean {\n\tif (typeof process === \"undefined\") {\n\t\treturn false;\n\t}\n\n\treturn process.env.DISABLE_ANALYTICS === \"true\" || process.env.NEXT_PUBLIC_DISABLE_ANALYTICS === \"true\";\n}\n\n/**\n * Default configuration for analytics\n * API host and key are hardcoded, but disabled flag can be set via environment variables\n */\nexport const defaultConfig: Partial<AnalyticsConfig> = {\n\t// Always use the Cloudflare Worker reverse proxy\n\tapiHost: POSTHOG_PROXY_HOST,\n\n\t// Analytics enabled by default (can be disabled via env var or config option)\n\tdisabled: false,\n\n\t// Debug mode disabled by default\n\tdebug: false,\n\n\t// Default PostHog options\n\tposthogOptions: {\n\t\t// Disable session recording by default (can be enabled per-app)\n\t\tdisable_session_recording: true,\n\n\t\t// Disable autocapture to keep events clean and intentional\n\t\tautocapture: false,\n\n\t\t// Disable automatic pageview capture (apps can enable if needed)\n\t\tcapture_pageview: false,\n\n\t\t// Disable pageleave events\n\t\tcapture_pageleave: false,\n\n\t\t// Don't use cross-subdomain cookies\n\t\tcross_subdomain_cookie: false,\n\n\t\t// Enable feature flags by default\n\t\tadvanced_disable_feature_flags: false,\n\n\t\t// Send feature flag events\n\t\tadvanced_disable_feature_flags_on_first_load: false,\n\n\t\t// Enable persistence for feature flags\n\t\tpersistence: \"localStorage+cookie\",\n\t},\n};\n\n/**\n * Merge user config with defaults\n *\n * Note: This package is designed to work exclusively with the Cloudflare Worker\n * reverse proxy (ph-relay.networkcanvas.com). Authentication is handled by the\n * worker, so the API key is optional and defaults to a placeholder value.\n *\n * The only environment variable checked is DISABLE_ANALYTICS / NEXT_PUBLIC_DISABLE_ANALYTICS\n * for disabling tracking. All other configuration is hardcoded or passed explicitly.\n */\nexport function mergeConfig(userConfig: AnalyticsConfig): Required<AnalyticsConfig> {\n\treturn {\n\t\tapiHost: userConfig.apiHost ?? defaultConfig.apiHost ?? POSTHOG_PROXY_HOST,\n\t\tapiKey: userConfig.apiKey ?? PROXY_MODE_DUMMY_KEY,\n\t\tinstallationId: userConfig.installationId,\n\t\tdisabled: userConfig.disabled ?? isDisabledByEnv() ?? defaultConfig.disabled ?? false,\n\t\tdebug: userConfig.debug ?? defaultConfig.debug ?? false,\n\t\tposthogOptions: {\n\t\t\t...defaultConfig.posthogOptions,\n\t\t\t...userConfig.posthogOptions,\n\t\t},\n\t};\n}\n","// Helper function that ensures that a value is an Error\nexport function ensureError(value: unknown): Error {\n\tif (!value) return new Error(\"No value was thrown\");\n\n\tif (value instanceof Error) return value;\n\n\t// Test if value inherits from Error\n\tif (Object.prototype.isPrototypeOf.call(value, Error)) return value as Error & typeof value;\n\n\tlet stringified = \"[Unable to stringify the thrown value]\";\n\ttry {\n\t\tstringified = JSON.stringify(value);\n\t} catch (e) {\n\t\t// biome-ignore lint/suspicious/noConsole: logging\n\t\tconsole.error(e);\n\t}\n\n\tconst error = new Error(`This value was thrown as is, not through an Error: ${stringified}`);\n\treturn error;\n}\n"],"mappings":";AAMA,IAAM,qBAAqB;AAQ3B,IAAM,uBAAuB;AAKtB,SAAS,kBAA2B;AAC1C,MAAI,OAAO,YAAY,aAAa;AACnC,WAAO;AAAA,EACR;AAEA,SAAO,QAAQ,IAAI,sBAAsB,UAAU,QAAQ,IAAI,kCAAkC;AAClG;AAMO,IAAM,gBAA0C;AAAA;AAAA,EAEtD,SAAS;AAAA;AAAA,EAGT,UAAU;AAAA;AAAA,EAGV,OAAO;AAAA;AAAA,EAGP,gBAAgB;AAAA;AAAA,IAEf,2BAA2B;AAAA;AAAA,IAG3B,aAAa;AAAA;AAAA,IAGb,kBAAkB;AAAA;AAAA,IAGlB,mBAAmB;AAAA;AAAA,IAGnB,wBAAwB;AAAA;AAAA,IAGxB,gCAAgC;AAAA;AAAA,IAGhC,8CAA8C;AAAA;AAAA,IAG9C,aAAa;AAAA,EACd;AACD;AAYO,SAAS,YAAY,YAAwD;AACnF,SAAO;AAAA,IACN,SAAS,WAAW,WAAW,cAAc,WAAW;AAAA,IACxD,QAAQ,WAAW,UAAU;AAAA,IAC7B,gBAAgB,WAAW;AAAA,IAC3B,UAAU,WAAW,YAAY,gBAAgB,KAAK,cAAc,YAAY;AAAA,IAChF,OAAO,WAAW,SAAS,cAAc,SAAS;AAAA,IAClD,gBAAgB;AAAA,MACf,GAAG,cAAc;AAAA,MACjB,GAAG,WAAW;AAAA,IACf;AAAA,EACD;AACD;;;AC1FO,SAAS,YAAY,OAAuB;AAClD,MAAI,CAAC,MAAO,QAAO,IAAI,MAAM,qBAAqB;AAElD,MAAI,iBAAiB,MAAO,QAAO;AAGnC,MAAI,OAAO,UAAU,cAAc,KAAK,OAAO,KAAK,EAAG,QAAO;AAE9D,MAAI,cAAc;AAClB,MAAI;AACH,kBAAc,KAAK,UAAU,KAAK;AAAA,EACnC,SAAS,GAAG;AAEX,YAAQ,MAAM,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,IAAI,MAAM,sDAAsD,WAAW,EAAE;AAC3F,SAAO;AACR;","names":[]}