@searchspring/snap-controller 0.64.0 → 0.65.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.
@@ -1,5 +1,4 @@
1
1
  import deepmerge from 'deepmerge';
2
- import { BeaconType, BeaconCategory, ProfilePlacement } from '@searchspring/snap-tracker';
3
2
  import { ErrorType } from '@searchspring/snap-store-mobx';
4
3
  import { AbstractController } from '../Abstract/AbstractController';
5
4
  import { ControllerTypes } from '../types';
@@ -15,307 +14,75 @@ export class RecommendationController extends AbstractController {
15
14
  super(config, { client, store, urlManager, eventManager, profiler, logger, tracker }, context);
16
15
  this.type = ControllerTypes.recommendation;
17
16
  this.events = {
18
- click: undefined,
19
- impression: undefined,
20
- render: undefined,
21
17
  product: {},
22
18
  };
23
- this.track = (() => {
24
- const getSeed = () => {
25
- let skus = [];
26
- switch (this.store.profile.placement) {
27
- case ProfilePlacement.PRODUCTPAGE:
28
- if (this.config.globals?.product) {
29
- skus = [this.config.globals?.product];
30
- }
31
- else if (this.config.globals?.products) {
32
- skus = this.config.globals?.products;
33
- }
34
- break;
35
- case ProfilePlacement.BASKETPAGE:
36
- skus = this.tracker.cookies.cart.get(); // this is an array
37
- break;
38
- default:
19
+ this.track = {
20
+ product: {
21
+ clickThrough: (e, result) => {
22
+ if (this.events.product[result.id]?.clickThrough)
39
23
  return;
40
- }
41
- if (skus.length) {
42
- return skus.map((sku) => ({
43
- sku,
44
- }));
45
- }
46
- };
47
- return {
48
- product: {
49
- click: (e, result) => {
50
- if (!this.store.profile.tag || !result)
51
- return;
52
- //set the profile click every time
53
- this.track.click(e);
54
- const payload = {
55
- type: BeaconType.PROFILE_PRODUCT_CLICK,
56
- category: BeaconCategory.RECOMMENDATIONS,
57
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
58
- event: {
59
- context: {
60
- action: 'navigate',
61
- placement: this.store.profile.placement,
62
- tag: this.store.profile.tag,
63
- type: 'product-recommendation',
64
- },
65
- product: {
66
- id: result.id,
67
- mappings: {
68
- core: result.mappings.core,
69
- },
70
- seed: getSeed(),
71
- },
72
- },
73
- pid: this.events.click?.id,
74
- };
75
- const event = this.tracker.track.event(payload);
76
- this.eventManager.fire('track.product.click', { controller: this, event: e, result, trackEvent: event });
77
- return event;
78
- },
79
- impression: (result) => {
80
- if (!this.store.profile.tag || !result || !this.events.impression || (this.events.product && this.events.product[result.id]?.impression))
81
- return;
82
- const payload = {
83
- type: BeaconType.PROFILE_PRODUCT_IMPRESSION,
84
- category: BeaconCategory.RECOMMENDATIONS,
85
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
86
- event: {
87
- context: {
88
- placement: this.store.profile.placement,
89
- tag: this.store.profile.tag,
90
- type: 'product-recommendation',
91
- },
92
- product: {
93
- id: result.id,
94
- mappings: {
95
- core: result.mappings.core,
96
- },
97
- seed: getSeed(),
98
- },
99
- },
100
- pid: this.events.impression.id,
101
- };
102
- this.events.product[result.id] = this.events.product[result.id] || {};
103
- const event = (this.events.product[result.id].impression = this.tracker.track.event(payload));
104
- this.eventManager.fire('track.product.impression', { controller: this, result, trackEvent: event });
105
- return event;
106
- },
107
- render: (result) => {
108
- if (!this.store.profile.tag || !result || !this.events.render || this.events.product[result.id]?.render)
109
- return;
110
- const payload = {
111
- type: BeaconType.PROFILE_PRODUCT_RENDER,
112
- category: BeaconCategory.RECOMMENDATIONS,
113
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
114
- event: {
115
- context: {
116
- placement: this.store.profile.placement,
117
- tag: this.store.profile.tag,
118
- type: 'product-recommendation',
119
- },
120
- product: {
121
- id: result.id,
122
- mappings: {
123
- core: result.mappings.core,
124
- },
125
- seed: getSeed(),
126
- },
127
- },
128
- pid: this.events.render.id,
129
- };
130
- this.events.product[result.id] = this.events.product[result.id] || {};
131
- const event = (this.events.product[result.id].render = this.tracker.track.event(payload));
132
- this.eventManager.fire('track.product.render', { controller: this, result, trackEvent: event });
133
- return event;
134
- },
135
- removedFromBundle: (result) => {
136
- if (!this.store.profile.tag ||
137
- !result ||
138
- !this.events.render ||
139
- !this.events.product[result.id]?.render ||
140
- this.store.profile.type != 'bundle')
141
- return;
142
- const payload = {
143
- type: BeaconType.PROFILE_PRODUCT_REMOVEDFROMBUNDLE,
144
- category: BeaconCategory.RECOMMENDATIONS,
145
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
146
- event: {
147
- context: {
148
- placement: this.store.profile.placement,
149
- tag: this.store.profile.tag,
150
- type: 'product-recommendation',
151
- },
152
- product: {
153
- id: result.id,
154
- mappings: {
155
- core: result.mappings.core,
156
- },
157
- seed: getSeed(),
158
- },
159
- },
160
- pid: this.events.click?.id,
161
- };
162
- this.events.product[result.id] = this.events.product[result.id] || {};
163
- const event = (this.events.product[result.id].render = this.tracker.track.event(payload));
164
- this.eventManager.fire('track.product.removedFromBundle', { controller: this, result, trackEvent: event });
165
- return event;
166
- },
167
- addedToBundle: (result) => {
168
- if (!this.store.profile.tag ||
169
- !result ||
170
- !this.events.render ||
171
- !this.events.product[result.id]?.render ||
172
- this.store.profile.type != 'bundle')
173
- return;
174
- const payload = {
175
- type: BeaconType.PROFILE_PRODUCT_ADDEDTOBUNDLE,
176
- category: BeaconCategory.RECOMMENDATIONS,
177
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
178
- event: {
179
- context: {
180
- placement: this.store.profile.placement,
181
- tag: this.store.profile.tag,
182
- type: 'product-recommendation',
183
- },
184
- product: {
185
- id: result.id,
186
- mappings: {
187
- core: result.mappings.core,
188
- },
189
- seed: getSeed(),
190
- },
191
- },
192
- pid: this.events.click?.id,
193
- };
194
- this.events.product[result.id] = this.events.product[result.id] || {};
195
- const event = (this.events.product[result.id].render = this.tracker.track.event(payload));
196
- this.eventManager.fire('track.product.addedToBundle', { controller: this, result, trackEvent: event });
197
- return event;
198
- },
24
+ const data = getRecommendationsSchemaData({ store: this.store, results: [result] });
25
+ this.tracker.events.recommendations.clickThrough({ data, siteId: this.config.globals?.siteId });
26
+ this.events.product[result.id] = this.events.product[result.id] || {};
27
+ this.events.product[result.id].clickThrough = true;
28
+ this.eventManager.fire('track.product.clickThrough', { controller: this, event: e, products: [result], trackEvent: data });
199
29
  },
200
- addBundle: (e, results) => {
201
- if (!results.length || !this.store.profile.tag || this.store.profile.type != 'bundle')
202
- return;
203
- const event = this.tracker.track.event({
204
- type: BeaconType.PROFILE_ADDBUNDLE,
205
- category: BeaconCategory.RECOMMENDATIONS,
206
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
207
- event: {
208
- context: {
209
- placement: this.store.profile.placement,
210
- tag: this.store.profile.tag,
211
- type: 'product-recommendation',
212
- },
213
- products: results.map((result) => ({
214
- id: result.id,
215
- mappings: {
216
- core: result.mappings.core,
217
- },
218
- quantity: result.quantity,
219
- })),
220
- profile: {
221
- tag: this.store.profile.tag,
222
- placement: this.store.profile.placement,
223
- threshold: this.store.profile.display.threshold,
224
- templateId: this.store.profile.display.template.uuid,
225
- seed: getSeed(),
226
- },
227
- },
228
- });
229
- this.eventManager.fire('track.addBundle', { controller: this, event: e, trackEvent: event });
230
- return event;
30
+ click: (e, result) => {
31
+ // TODO: closest might be going too far - write own function to only go n levels up - additionally check that href includes result.url
32
+ const href = e.target?.getAttribute('href') || e.target?.closest('a')?.getAttribute('href');
33
+ if (href) {
34
+ this.track.product.clickThrough(e, result);
35
+ }
36
+ else {
37
+ // TODO: in future, send as an interaction event
38
+ }
231
39
  },
232
- click: (e) => {
233
- if (!this.store.profile.tag)
40
+ impression: (result) => {
41
+ if (this.events.product[result.id]?.impression)
234
42
  return;
235
- const event = this.tracker.track.event({
236
- type: BeaconType.PROFILE_CLICK,
237
- category: BeaconCategory.RECOMMENDATIONS,
238
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
239
- event: {
240
- context: {
241
- action: 'navigate',
242
- placement: this.store.profile.placement,
243
- tag: this.store.profile.tag,
244
- type: 'product-recommendation',
245
- },
246
- profile: {
247
- tag: this.store.profile.tag,
248
- placement: this.store.profile.placement,
249
- threshold: this.store.profile.display.threshold,
250
- templateId: this.store.profile.display.template.uuid,
251
- seed: getSeed(),
252
- },
253
- },
254
- });
255
- this.events.click = event;
256
- this.eventManager.fire('track.click', { controller: this, event: e, trackEvent: event });
257
- return event;
43
+ const data = getRecommendationsSchemaData({ store: this.store, results: [result] });
44
+ this.tracker.events.recommendations.impression({ data, siteId: this.config.globals?.siteId });
45
+ this.events.product[result.id] = this.events.product[result.id] || {};
46
+ this.events.product[result.id].impression = true;
47
+ this.eventManager.fire('track.product.impression', { controller: this, products: [result], trackEvent: data });
48
+ return data;
258
49
  },
259
- impression: () => {
260
- if (!this.store.profile.tag || this.events.impression)
50
+ render: (result) => {
51
+ if (this.events.product[result.id]?.render)
261
52
  return;
262
- const event = this.tracker.track.event({
263
- type: BeaconType.PROFILE_IMPRESSION,
264
- category: BeaconCategory.RECOMMENDATIONS,
265
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
266
- event: {
267
- context: {
268
- placement: this.store.profile.placement,
269
- tag: this.store.profile.tag,
270
- type: 'product-recommendation',
271
- },
272
- profile: {
273
- tag: this.store.profile.tag,
274
- placement: this.store.profile.placement,
275
- threshold: this.store.profile.display.threshold,
276
- templateId: this.store.profile.display.template.uuid,
277
- seed: getSeed(),
278
- },
279
- },
280
- });
281
- this.events.impression = event;
282
- this.eventManager.fire('track.impression', { controller: this, trackEvent: event });
283
- return event;
53
+ const data = getRecommendationsSchemaData({ store: this.store, results: [result] });
54
+ this.tracker.events.recommendations.render({ data, siteId: this.config.globals?.siteId });
55
+ this.events.product[result.id] = this.events.product[result.id] || {};
56
+ this.events.product[result.id].render = true;
57
+ this.eventManager.fire('track.product.render', { controller: this, products: [result], trackEvent: data });
58
+ return data;
284
59
  },
285
- render: () => {
286
- if (!this.store.profile.tag || this.events.render)
60
+ addToCart: (result) => {
61
+ const data = getRecommendationsSchemaData({ store: this.store, results: [result] });
62
+ this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
63
+ this.eventManager.fire('track.product.addToCart', { controller: this, products: [result], trackEvent: data });
64
+ return data;
65
+ },
66
+ },
67
+ bundle: {
68
+ addToCart: (results) => {
69
+ if (this.store.profile.type != 'bundle')
287
70
  return;
288
- const event = this.tracker.track.event({
289
- type: BeaconType.PROFILE_RENDER,
290
- category: BeaconCategory.RECOMMENDATIONS,
291
- context: this.config.globals?.siteId ? { website: { trackingCode: this.config.globals?.siteId } } : undefined,
292
- event: {
293
- context: {
294
- placement: this.store.profile.placement,
295
- tag: this.store.profile.tag,
296
- type: 'product-recommendation',
297
- },
298
- profile: {
299
- tag: this.store.profile.tag,
300
- placement: this.store.profile.placement,
301
- threshold: this.store.profile.display.threshold,
302
- templateId: this.store.profile.display.template.uuid,
303
- seed: getSeed(),
304
- },
305
- },
306
- });
307
- this.events.render = event;
308
- this.eventManager.fire('track.render', { controller: this, trackEvent: event });
309
- return event;
71
+ const data = getRecommendationsSchemaData({ store: this.store, results });
72
+ this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
73
+ this.eventManager.fire('track.bundle.addToCart', { controller: this, products: results, trackEvent: data });
74
+ return data;
310
75
  },
311
- };
312
- })();
76
+ },
77
+ };
313
78
  this.search = async () => {
314
79
  try {
315
80
  if (!this.initialized) {
316
81
  await this.init();
317
82
  }
318
83
  const params = this.params;
84
+ // reset events for new search
85
+ this.events = { product: {} };
319
86
  this.store.loading = true;
320
87
  try {
321
88
  await this.eventManager.fire('beforeSearch', {
@@ -443,20 +210,16 @@ export class RecommendationController extends AbstractController {
443
210
  this.config = deepmerge(defaultConfig, this.config);
444
211
  this.store.setConfig(this.config);
445
212
  // add 'afterStore' middleware
446
- this.eventManager.on('afterStore', async (recommend, next) => {
447
- await next();
448
- // attach tracking events to cart store
449
- this.store.cart?.on('addItems', ({ items }) => {
450
- items.forEach((item) => {
451
- this.track.product.addedToBundle(item);
452
- });
453
- });
454
- this.store.cart?.on('removeItems', ({ items }) => {
455
- items.forEach((item) => {
456
- this.track.product.removedFromBundle(item);
457
- });
458
- });
459
- });
213
+ // this.eventManager.on('afterStore', async (recommend: AfterStoreObj, next: Next): Promise<void | boolean> => {
214
+ // await next();
215
+ // // attach tracking events to cart store
216
+ // this.store.cart?.on('addItems', ({ items }: { items: Product[] }) => {
217
+ // // add to bundle
218
+ // });
219
+ // this.store.cart?.on('removeItems', ({ items }: { items: Product[] }) => {
220
+ // // remove from bundle
221
+ // });
222
+ // });
460
223
  // attach config plugins and event middleware
461
224
  this.use(this.config);
462
225
  }
@@ -468,13 +231,13 @@ export class RecommendationController extends AbstractController {
468
231
  batchId: this.config.batchId,
469
232
  ...this.config.globals,
470
233
  };
471
- const shopperId = this.tracker.getContext().shopperId;
472
- const cart = this.tracker.cookies.cart.get();
473
- const lastViewed = this.tracker.cookies.viewed.get();
234
+ const { shopperId } = this.tracker.getContext();
474
235
  if (shopperId) {
475
236
  params.shopper = shopperId;
476
237
  }
477
238
  if (!params.siteId || params.siteId == this.tracker.getGlobals().siteId) {
239
+ const cart = this.tracker.cookies.cart.get();
240
+ const lastViewed = this.tracker.cookies.viewed.get();
478
241
  if (cart?.length) {
479
242
  params.cart = cart;
480
243
  }
@@ -485,3 +248,17 @@ export class RecommendationController extends AbstractController {
485
248
  return params;
486
249
  }
487
250
  }
251
+ function getRecommendationsSchemaData({ store, results }) {
252
+ return {
253
+ tag: store.profile.tag,
254
+ results: results?.map((result) => {
255
+ const core = result.mappings.core;
256
+ return {
257
+ uid: core.uid || '',
258
+ // childUid: core.uid,
259
+ sku: core.sku,
260
+ // childSku: core.sku,
261
+ };
262
+ }) || [],
263
+ };
264
+ }
@@ -1,14 +1,18 @@
1
1
  import { AbstractController } from '../Abstract/AbstractController';
2
2
  import { StorageStore } from '@searchspring/snap-store-mobx';
3
3
  import { ControllerTypes } from '../types';
4
- import type { BeaconEvent } from '@searchspring/snap-tracker';
5
- import type { SearchStore } from '@searchspring/snap-store-mobx';
4
+ import type { Product, Banner, SearchStore } from '@searchspring/snap-store-mobx';
6
5
  import type { SearchControllerConfig, ControllerServices, ContextVariables } from '../types';
7
6
  import type { SearchRequestModel } from '@searchspring/snapi-types';
8
7
  type SearchTrackMethods = {
9
8
  product: {
10
- click: (e: MouseEvent, result: any) => BeaconEvent | undefined;
9
+ clickThrough: (e: MouseEvent, result: Product) => void;
10
+ click: (e: MouseEvent, result: Product | Banner) => void;
11
+ render: (result: Product) => void;
12
+ impression: (result: Product) => void;
13
+ addToCart: (results: Product) => void;
11
14
  };
15
+ redirect: (redirectURL: string) => void;
12
16
  };
13
17
  export declare class SearchController extends AbstractController {
14
18
  type: ControllerTypes;
@@ -16,10 +20,13 @@ export declare class SearchController extends AbstractController {
16
20
  config: SearchControllerConfig;
17
21
  storage: StorageStore;
18
22
  private previousResults;
23
+ private pageType;
24
+ private events;
19
25
  constructor(config: SearchControllerConfig, { client, store, urlManager, eventManager, profiler, logger, tracker }: ControllerServices, context?: ContextVariables);
20
26
  track: SearchTrackMethods;
21
27
  get params(): SearchRequestModel;
22
28
  search: () => Promise<void>;
29
+ addToCart: (product: Product) => Promise<void>;
23
30
  }
24
31
  export declare function getStorableRequestParams(request: SearchRequestModel): SearchRequestModel;
25
32
  export declare function generateHrefSelector(element: HTMLElement, href: string, levels?: number): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"SearchController.d.ts","sourceRoot":"","sources":["../../../src/Search/SearchController.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAa,MAAM,+BAA+B,CAAC;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EACX,sBAAsB,EAGtB,kBAAkB,EAClB,gBAAgB,EAGhB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EACX,kBAAkB,EAKlB,MAAM,2BAA2B,CAAC;AAmBnC,KAAK,kBAAkB,GAAG;IACzB,OAAO,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,KAAK,WAAW,GAAG,SAAS,CAAC;KAC/D,CAAC;CACF,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,kBAAkB;IAChD,IAAI,kBAA0B;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,sBAAsB,CAAC;IACvC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,eAAe,CAAwC;gBAG9D,MAAM,EAAE,sBAAsB,EAC9B,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAC1F,OAAO,CAAC,EAAE,gBAAgB;IA2J3B,KAAK,EAAE,kBAAkB,CA+CvB;IAEF,IAAI,MAAM,IAAI,kBAAkB,CAiD/B;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CAoN9B;CACF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAiBxF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,MAAM,GAAG,SAAS,CAgCvG"}
1
+ {"version":3,"file":"SearchController.d.ts","sourceRoot":"","sources":["../../../src/Search/SearchController.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAa,MAAM,+BAA+B,CAAC;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAqB,MAAM,+BAA+B,CAAC;AACrG,OAAO,KAAK,EACX,sBAAsB,EAGtB,kBAAkB,EAClB,gBAAgB,EAIhB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EACX,kBAAkB,EAOlB,MAAM,2BAA2B,CAAC;AA8BnC,KAAK,kBAAkB,GAAG;IACzB,OAAO,EAAE;QACR,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QACvD,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QACzD,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QAClC,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QACtC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;KACtC,CAAC;IACF,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,kBAAkB;IAChD,IAAI,kBAA0B;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,sBAAsB,CAAC;IACvC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,MAAM,CASM;gBAGnB,MAAM,EAAE,sBAAsB,EAC9B,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAC1F,OAAO,CAAC,EAAE,gBAAgB;IAmM3B,KAAK,EAAE,kBAAkB,CA0FvB;IAEF,IAAI,MAAM,IAAI,kBAAkB,CA8C/B;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CAuN9B;IAEF,SAAS,YAAmB,OAAO,KAAG,QAAQ,IAAI,CAAC,CAGjD;CACF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAiBxF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,MAAM,GAAG,SAAS,CAgCvG"}