zz-shopify-components 0.12.0 → 0.12.1-beta.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.
@@ -0,0 +1,361 @@
1
+ class BuyNowBottomBar extends HTMLElement {
2
+ loading = false;
3
+ bindProducts = [];
4
+ mainProductId = '';
5
+ currency = '';
6
+ showMore = false;
7
+ isEduProduct = false;
8
+ isLoggedIn = false;
9
+ isEduVerified = false;
10
+ eduPageUrl = '';
11
+ constructor() {
12
+ super();
13
+ this.cart =
14
+ document.querySelector('cart-notification') ||
15
+ document.querySelector('cart-drawer');
16
+ this.careGuide = this.dataset.careGuide;
17
+ this.hasCare = this.dataset.hasCare || false;
18
+ this.init();
19
+ this.currency = this.dataset.currency;
20
+ this.isEduProduct = this.dataset.isEduProduct === 'true';
21
+ this.isLoggedIn = this.dataset.isLoggedIn === 'true';
22
+ this.isEduVerified = this.dataset.isEduVerified === 'true';
23
+ this.eduPageUrl = this.dataset.eduPageUrl;
24
+ }
25
+
26
+ init() {
27
+ this.querySelector('button').addEventListener('click', async () => {
28
+ this.handleToPay();
29
+ });
30
+
31
+ if (this.querySelector('.show-more-btn')) {
32
+ this.querySelector('.show-more-btn').addEventListener(
33
+ 'click',
34
+ async () => {
35
+ this.toggleShowMoreDesc();
36
+ }
37
+ );
38
+ }
39
+ }
40
+ handleAddToCart() {
41
+ this.getMainProduct();
42
+ this.getBindProduct();
43
+ this.onSubmitHandler();
44
+ }
45
+ showCareChooseDialog() {
46
+ const careDialog = document.querySelector(
47
+ 'product-detail-dialog-hovercare-choose'
48
+ );
49
+ if (careDialog) {
50
+ careDialog.showModal();
51
+ }
52
+ }
53
+
54
+ handleToPay() {
55
+ this.getMainProduct();
56
+ const cartUrl = `/cart/${this.mainProductId}:${this.mainProductQuantity} `
57
+ window.location.href = cartUrl;
58
+ }
59
+
60
+ async onSubmitHandler() {
61
+
62
+ const data = {
63
+ items: [
64
+ {
65
+ id: this.mainProductId,
66
+ quantity: 1,
67
+ },
68
+ ...this.bindProducts,
69
+ ],
70
+ sections: 'cart-drawer,cart-icon-bubble',
71
+ };
72
+
73
+ this.toggleLoading();
74
+ await fetch(`${routes.cart_add_url}`, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Content-Type': 'application/json',
78
+ Accept: 'application/javascript',
79
+ },
80
+ body: JSON.stringify(data),
81
+ })
82
+ .then((response) => response.json())
83
+ .then((response) => {
84
+ if (response.status) {
85
+ publish(PUB_SUB_EVENTS.cartError, {
86
+ source: 'product-form',
87
+ productVariantId: this.mainProductId,
88
+ errors: response.errors || response.description,
89
+ message: response.message,
90
+ });
91
+ } else if (!this.cart) {
92
+ window.location = window.routes.cart_url;
93
+ return;
94
+ }
95
+
96
+ if (!this.error)
97
+ publish(PUB_SUB_EVENTS.cartUpdate, {
98
+ source: 'product-form',
99
+ productVariantId: this.mainProductId,
100
+ cartData: response,
101
+ });
102
+ this.error = false;
103
+ const quickAddModal = this.closest('quick-add-modal');
104
+ if (quickAddModal) {
105
+ document.body.addEventListener(
106
+ 'modalClosed',
107
+ () => {
108
+ setTimeout(() => {
109
+ this.cart.renderContents(response);
110
+ });
111
+ },
112
+ { once: true }
113
+ );
114
+ quickAddModal.hide(true);
115
+ } else {
116
+ this.cart.renderContents(response);
117
+ }
118
+ })
119
+ .catch((e) => {
120
+ console.error(e);
121
+ })
122
+ .finally(() => {
123
+ if (this.cart && this.cart.classList.contains('is-empty'))
124
+ this.cart.classList.remove('is-empty');
125
+ this.toggleLoading();
126
+ });
127
+ }
128
+
129
+ getMainProduct() {
130
+ this.mainProductId =
131
+ document.querySelector('product-buy-now-selector').currentVariantId;
132
+ this.mainProductQuantity =
133
+ document.querySelector('product-buy-now-counter input').value;
134
+ }
135
+ getBindProduct() {
136
+ this.bindProducts = [];
137
+ const tags = Array.from(document.querySelectorAll('accessory-product'));
138
+ tags.forEach((item) => {
139
+ if (item.isSelected) {
140
+ this.bindProducts.push({
141
+ id: item.currentId,
142
+ quantity: item.getNumber(),
143
+ });
144
+ }
145
+ });
146
+ const careTags = Array.from(document.querySelectorAll('care-product'));
147
+ if (careTags && careTags.length) {
148
+ careTags.forEach((item) => {
149
+ if (item.isShow && item.isSelected) {
150
+ this.bindProducts.push({
151
+ id: item.currentId,
152
+ quantity: 1,
153
+ });
154
+ }
155
+ });
156
+ }
157
+
158
+ const careDialog = document.querySelector(
159
+ 'product-detail-dialog-hovercare-choose'
160
+ );
161
+ if (careDialog && careDialog.chooseId) {
162
+ this.bindProducts.push({
163
+ id: careDialog.chooseId,
164
+ quantity: 1,
165
+ });
166
+ }
167
+ }
168
+
169
+ toggleLoading() {
170
+ this.loading = !this.loading;
171
+ this.querySelector('.buy-loading').classList.toggle('tw-hidden');
172
+ }
173
+
174
+ toggleShowMoreDesc() {
175
+ const tipEl = this.querySelector('.bar-tip');
176
+ const svg = this.querySelector('.show-more-btn');
177
+
178
+ this.showMore = !this.showMore;
179
+
180
+ gsap.to(svg, {
181
+ rotation: this.showMore ? 180 : 0,
182
+ duration: 0.3,
183
+ });
184
+
185
+ tipEl.classList.toggle('show-more-desc', this.showMore);
186
+ }
187
+
188
+ // available: "true" or "false"
189
+ toggleAddToCartButton(available) {
190
+ const button = this.querySelector('button');
191
+ const btnText = this.querySelector('.buy-now-bottom-bar-btn-text');
192
+ if (!button) {
193
+ return;
194
+ }
195
+
196
+ if (button.disabled && available === 'true') {
197
+ button.disabled = false;
198
+ btnText.textContent = this.dataset.btnText;
199
+ } else if (!button.disabled && available === 'false') {
200
+ button.disabled = true;
201
+ btnText.textContent = this.dataset.btnOutOfStockText;
202
+ }
203
+ }
204
+
205
+ updatePrice() {
206
+ if (!window.Decimal) {
207
+ return;
208
+ }
209
+ const main = document
210
+ .querySelector('.product-version-option input:checked')
211
+ ?.closest('.product-version-option');
212
+
213
+ const {
214
+ price: mainPrice,
215
+ before: mainBefore,
216
+ available: mainAvailable,
217
+ } = main.dataset;
218
+
219
+ let total_price = new Decimal(this.handlePrice(mainPrice));
220
+ let total_before = new Decimal(this.handlePrice(mainBefore));
221
+
222
+ this.toggleAddToCartButton(mainAvailable);
223
+
224
+ // 找到被选中的附加产品
225
+ const selectedAccessories = Array.from(
226
+ document.querySelectorAll('accessory-product')
227
+ ).filter((item) => item.isSelected);
228
+
229
+ for (const item of selectedAccessories) {
230
+ const count = parseInt(item.getNumber(), 10);
231
+ const priceEl = item.querySelector('.current-price');
232
+ if (!priceEl) {
233
+ console.warn('找不到 .current-price 元素');
234
+ continue;
235
+ }
236
+
237
+ const { price, before } = priceEl.dataset;
238
+
239
+ const currentPrice = new Decimal(this.handlePrice(price));
240
+ const originPrice = new Decimal(this.handlePrice(before));
241
+
242
+ total_price = total_price.plus(currentPrice.times(count));
243
+
244
+ const actualBefore = currentPrice.greaterThan(originPrice)
245
+ ? currentPrice.times(count)
246
+ : originPrice.times(count);
247
+ total_before = total_before.plus(actualBefore);
248
+ }
249
+
250
+ // 找到care 商品
251
+ const bindCares = Array.from(document.querySelectorAll('care-product'));
252
+ bindCares.forEach((item) => {
253
+ const count = parseInt(item.getNumber(), 10) || 0; // 确保 count 是有效数字
254
+
255
+ if (item.isShow && item.isSelected) {
256
+ const priceEl = item.querySelector('.current-price');
257
+ if (priceEl) {
258
+ const { price, before } = priceEl.dataset;
259
+
260
+ const currentPrice = new Decimal(this.handlePrice(price));
261
+ const originPrice = new Decimal(this.handlePrice(before));
262
+
263
+ total_price = total_price.plus(currentPrice.times(count));
264
+
265
+ const actualBefore = currentPrice.lessThan(originPrice)
266
+ ? originPrice.times(count)
267
+ : currentPrice.times(count);
268
+ total_before = total_before.plus(actualBefore);
269
+ } else {
270
+ console.warn('找不到 .current-price 元素');
271
+ }
272
+ }
273
+ });
274
+
275
+ // 计算加个
276
+ const priceText = this.currency + total_price.toString();
277
+ const beforeText = this.currency + total_before.toString();
278
+ const showBefore = total_before.greaterThan(total_price);
279
+ requestAnimationFrame(() => {
280
+ const priceEl = this.querySelector('.price');
281
+ const beforeEl = this.querySelector('.before');
282
+ const mbPriceEl = this.querySelector('.mb_price');
283
+ const mbBeforeEl = this.querySelector('.mb_before');
284
+
285
+ if (priceEl) priceEl.textContent = priceText;
286
+ if (beforeEl) beforeEl.textContent = showBefore ? beforeText : '';
287
+ if (mbPriceEl) mbPriceEl.textContent = priceText;
288
+ if (mbBeforeEl) mbBeforeEl.textContent = showBefore ? beforeText : '';
289
+
290
+ // 计算折扣
291
+ const discountEl = this.querySelectorAll('.price-discouter');
292
+ if (discountEl.length > 0) {
293
+ const percent = total_price.times(100).dividedBy(total_before);
294
+ const discountPercent = 100 - percent;
295
+ const discountValue = total_before.minus(total_price);
296
+
297
+ discountEl.forEach((el) => {
298
+ if (total_before.greaterThan(total_price)) {
299
+ el.classList.remove('tw-hidden');
300
+ el.textContent =
301
+ this.dataset.discountType === 'percent'
302
+ ? '- ' + Math.round(discountPercent) + '%'
303
+ : 'Save ' + this.currency + discountValue;
304
+ } else {
305
+ el.classList.add('tw-hidden');
306
+ }
307
+ });
308
+ }
309
+ });
310
+
311
+ // const toggleClass = (el, show) => {
312
+ // if (!el) return;
313
+ // el.classList.toggle('tw-hidden', !show);
314
+ // };
315
+
316
+ // toggleClass(beforeEl, showBefore);
317
+ // toggleClass(mbBeforeEl, showBefore);
318
+
319
+ this.updatePayPal(total_price.toString());
320
+ }
321
+ handlePrice(price) {
322
+ return price.toString().replace(/,/g, '') || 0;
323
+ }
324
+
325
+ updatePayPal(price) {
326
+ if (!price) return;
327
+
328
+ clearTimeout(this._paypalTimer);
329
+
330
+ const paypalEls = document.querySelectorAll('.product-buy-paypal');
331
+
332
+ if (!this.paypalLoading) {
333
+ this.paypalLoading = true;
334
+ paypalEls.forEach((el) => {
335
+ el.classList.add('tw-daisy-loading');
336
+ });
337
+ }
338
+
339
+ paypalEls.forEach((el) => {
340
+ el.dataset.ppAmount = price;
341
+ });
342
+
343
+ this._paypalTimer = setTimeout(() => {
344
+ paypalEls.forEach((el) => {
345
+ el.classList.remove('tw-daisy-loading');
346
+ });
347
+ this.paypalLoading = false;
348
+ }, 800);
349
+ }
350
+ // 有预期到货时间不展示
351
+ toggleShowTitleDesc(available) {
352
+ const barTitleDesc = this.querySelector('.bar-title-desc');
353
+ if (!available) {
354
+ barTitleDesc.classList.add('tw-hidden');
355
+ } else {
356
+ barTitleDesc.classList.remove('tw-hidden');
357
+ }
358
+ }
359
+ }
360
+
361
+ customElements.define('buy-now-bottom-bar', BuyNowBottomBar);
@@ -236,6 +236,14 @@ body.zz-dialog-open {
236
236
  background: #FC6C0F;
237
237
  color: #fff;
238
238
  }
239
+ .zz-btn.zz-btn-primary.zz-btn-blue {
240
+ background: #378DDD;
241
+ color: #fff;
242
+ }
243
+ .zz-btn.zz-btn-primary.zz-btn-yellow {
244
+ background: linear-gradient(139.02deg, #FFEB00 0.01%, #FFD400 100.55%);
245
+ color: #000000;
246
+ }
239
247
  .zz-btn.zz-btn-primary-round.zz-btn-black {
240
248
  background: #000000;
241
249
  color: #fff;
@@ -261,7 +269,16 @@ body.zz-dialog-open {
261
269
  color: #FC6C0F;
262
270
  border: 1.5px solid #FC6C0F;
263
271
  }
264
-
272
+ .zz-btn.zz-btn-default.zz-btn-yellow {
273
+ background: transparent;
274
+ color: #000;
275
+ border: 1.5px solid #FFEB00;
276
+ }
277
+ .zz-btn.zz-btn-default.zz-btn-blue {
278
+ background: transparent;
279
+ color: #378DDD;
280
+ border: 1.5px solid #378DDD;
281
+ }
265
282
  .zz-btn.zz-btn-round.zz-btn-black {
266
283
  background: transparent;
267
284
  color: #000000;
@@ -281,6 +298,18 @@ body.zz-dialog-open {
281
298
  border-radius: 30px;
282
299
  }
283
300
 
301
+ .zz-btn-link.zz-btn-blue {
302
+ color: #378DDD;
303
+ }
304
+ .zz-btn-link.zz-btn-orange {
305
+ color: #FC6C0F;
306
+ }
307
+ .zz-btn-link.zz-btn-white {
308
+ color: #fff;
309
+ }
310
+ .zz-btn-link.zz-btn-black {
311
+ color: #000000;
312
+ }
284
313
 
285
314
  .zz-btn:hover {
286
315
  opacity: 0.8;
@@ -328,6 +357,9 @@ body.zz-dialog-open {
328
357
  }
329
358
  }
330
359
 
360
+ .zz-btn-shape-round {
361
+ border-radius: 90px;
362
+ }
331
363
  .zz-video {
332
364
  border-radius: inherit;
333
365
  }
@@ -338,7 +370,7 @@ body.zz-dialog-open {
338
370
  display: grid;
339
371
  place-items: center;
340
372
  pointer-events: none; /* 不阻挡点击 */
341
- z-index: 2147483647; /* 最高层 */
373
+ z-index: 9999; /* 最高层 */
342
374
  }
343
375
  .zz-toast-box{
344
376
  pointer-events: auto;
@@ -347,25 +379,29 @@ body.zz-dialog-open {
347
379
  padding: 12px 16px;
348
380
  border-radius: 12px;
349
381
  background: #fff;
350
- color: #b42318; /* 错误红 */
351
- border: 1px solid rgba(180,35,24,.25);
352
- box-shadow: 0 10px 30px rgba(0,0,0,.15);
353
- font: 14px/1.4 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Microsoft Yahei',sans-serif;
354
- text-align: center;
382
+ color: #161110;
383
+ box-shadow: 0px 2px 16px 0px #00000026;
384
+ font-size: 14px;
385
+ text-align: left;
355
386
  word-break: break-word;
356
387
  transform: translateZ(0) scale(.96);
357
388
  opacity: 0;
358
389
  transition: transform .18s ease, opacity .18s ease;
390
+ line-height: 1.2;
391
+ display: inline-flex;
392
+
393
+ }
394
+ .zz-toast-icon {
395
+ margin-right: 4px;
396
+ padding: 0px 2px;
397
+ }
398
+ @media screen and (min-width: 1024px) {
399
+ .zz-toast-msg{
400
+ font-size: 16px;
401
+ }
402
+
359
403
  }
360
404
  .zz-toast-box.show{
361
405
  opacity: 1;
362
406
  transform: translateZ(0) scale(1);
363
407
  }
364
- .zz-toast-title{
365
- display:inline-block;
366
- margin-bottom: 2px;
367
- font-weight: 600;
368
- }
369
- .zz-toast-msg{
370
- display:block;
371
- }
@@ -172,6 +172,47 @@ if (!customElements.get('zz-video-button')) {
172
172
  let toastEl = null;
173
173
  let hideTimer = null;
174
174
 
175
+ const toastIcon = {
176
+ error: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
177
+ <g clip-path="url(#clip0_19382_20972)">
178
+ <circle cx="9" cy="9" r="9" fill="#FF4D4F"/>
179
+ <path d="M6.24231 6.17188L11.8992 11.8287" stroke="white" stroke-linecap="round"/>
180
+ <path d="M6.24231 11.8281L11.8992 6.17127" stroke="white" stroke-linecap="round"/>
181
+ </g>
182
+ <defs>
183
+ <clipPath id="clip0_19382_20972">
184
+ <rect width="18" height="18" fill="white"/>
185
+ </clipPath>
186
+ </defs>
187
+ </svg>
188
+ `,
189
+ success: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
190
+ <g clip-path="url(#clip0_19382_20921)">
191
+ <circle cx="9" cy="9" r="9" fill="#5BC726"/>
192
+ <path d="M5 9L7.82843 11.8284L12.7782 6.87868" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
193
+ </g>
194
+ <defs>
195
+ <clipPath id="clip0_19382_20921">
196
+ <rect width="18" height="18" fill="white"/>
197
+ </clipPath>
198
+ </defs>
199
+ </svg>
200
+ `,
201
+ warning: `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
202
+ <g clip-path="url(#clip0_19382_20989)">
203
+ <circle cx="9" cy="9" r="9" fill="#FAAD14"/>
204
+ <path d="M8.99994 5L8.99994 10" stroke="white" stroke-linecap="round"/>
205
+ <circle cx="9.00001" cy="12.8" r="0.8" fill="white"/>
206
+ </g>
207
+ <defs>
208
+ <clipPath id="clip0_19382_20989">
209
+ <rect width="18" height="18" fill="white"/>
210
+ </clipPath>
211
+ </defs>
212
+ </svg>
213
+ `,
214
+ };
215
+
175
216
  function createToast() {
176
217
  const layer = document.createElement('div');
177
218
  layer.className = 'zz-toast-layer';
@@ -182,51 +223,46 @@ if (!customElements.get('zz-video-button')) {
182
223
  box.className = 'zz-toast-box';
183
224
  box.setAttribute('role', 'alert');
184
225
 
185
- // const title = document.createElement('span');
186
- // title.className = 'zz-toast-title';
187
- // title.textContent = '错误';
188
-
189
226
  const msg = document.createElement('span');
190
227
  msg.className = 'zz-toast-msg';
191
228
 
192
- // box.appendChild(title);
229
+ const icon = document.createElement('span');
230
+ icon.className = 'zz-toast-icon';
231
+
232
+ box.appendChild(icon);
193
233
  box.appendChild(msg);
194
234
  layer.appendChild(box);
195
235
  document.body.appendChild(layer);
196
236
 
197
- return { layer, box, msg };
237
+ return { layer, box, msg, icon };
198
238
  }
199
239
 
200
240
  /**
201
241
  * 显示错误 Toast
202
242
  * @param {string} message - 要显示的错误文字
203
- * @param {{duration?:number,title?:string}} [opts]
243
+ * @param {{duration?:number,type?:string}} [opts]
204
244
  */
205
245
  function zzShowToast(message, opts = {}) {
206
-
207
246
  if (!toastEl) {
208
247
  toastEl = createToast();
209
248
  }
210
249
 
211
- // 可选标题(默认“错误”)
212
- if(opts.title) {
213
- toastEl.title.textContent = opts.title;
214
- }
215
-
216
250
  // 更新文本
217
251
  toastEl.msg.textContent = message ?? '';
252
+ if (opts.type) {
253
+ toastEl.icon.innerHTML = toastIcon[opts.type];
254
+ } else {
255
+ toastEl.icon.innerHTML = '';
256
+ }
218
257
 
219
258
  // 重新插入到 body 末尾,保证在最上层
220
259
  document.body.appendChild(toastEl.layer);
221
-
222
260
  // 显示动画
223
261
  requestAnimationFrame(() => {
224
262
  toastEl.box.classList.add('show');
225
263
  });
226
-
227
264
  // 清理上一次的计时器
228
265
  clearTimeout(hideTimer);
229
-
230
266
  const duration = Math.max(500, Number(opts.duration || 2000));
231
267
  hideTimer = setTimeout(() => {
232
268
  toastEl.box.classList.remove('show');
@@ -241,4 +277,3 @@ if (!customElements.get('zz-video-button')) {
241
277
  // 暴露到全局
242
278
  window.zzShowToast = zzShowToast;
243
279
  })();
244
-
@@ -0,0 +1,85 @@
1
+ document.addEventListener('DOMContentLoaded', (event) => {
2
+ if (!customElements.get('photo-swiper')) {
3
+ customElements.define(
4
+ 'photo-swiper',
5
+ class PhotoSwiper extends HTMLElement {
6
+ swiper = null; // swiper实例
7
+
8
+ constructor() {
9
+ super();
10
+ // 初始化swiper
11
+ // if (!this.swiper) {
12
+ // this.initSwiper();
13
+ // }
14
+ }
15
+
16
+ // connectedCallback() {
17
+ // this.initSwiper();
18
+ // }
19
+
20
+ disconnectedCallback() {
21
+ if (this.swiper) {
22
+ this.swiper.destroy();
23
+ this.swiper = null;
24
+ }
25
+ }
26
+
27
+ initSwiper() {
28
+ const swiperContainer = this.querySelector('.swiper');
29
+ if (!swiperContainer) return;
30
+
31
+ const slides = swiperContainer.querySelectorAll('.swiper-slide');
32
+ const imageCount = slides.length;
33
+
34
+ this.swiper = new Swiper(swiperContainer, {
35
+ slidesPerView: 1,
36
+ spaceBetween: 0,
37
+ pagination: {
38
+ el: this.querySelector('.swiper-pagination'),
39
+ type: 'bullets',
40
+ clickable: true,
41
+ dynamicBullets: false,
42
+ },
43
+ navigation: {
44
+ nextEl: '.swiper-button-next',
45
+ prevEl: '.swiper-button-prev',
46
+ },
47
+ effect: 'slide',
48
+ fadeEffect: {
49
+ crossFade: true,
50
+ },
51
+ loop: imageCount > 1,
52
+ observer: true,
53
+ observeParents: true,
54
+ speed: 500,
55
+ on: {
56
+ init: function () {
57
+ const paginationEl = this.pagination.el;
58
+ const prevButton = swiperContainer.querySelector(
59
+ '.swiper-button-prev'
60
+ );
61
+ const nextButton = swiperContainer.querySelector(
62
+ '.swiper-button-next'
63
+ );
64
+
65
+ if (imageCount <= 1) {
66
+ if (paginationEl) paginationEl.style.display = 'none';
67
+ if (prevButton) prevButton.style.display = 'none';
68
+ if (nextButton) nextButton.style.display = 'none';
69
+ }
70
+ },
71
+ },
72
+ });
73
+ }
74
+ // 销毁
75
+ destroySwiper() {
76
+ if (this.swiper) {
77
+ // 完全销毁swiper,包括所有事件监听器和DOM元素
78
+ this.swiper.destroy(true, true);
79
+ this.swiper = null;
80
+ }
81
+ }
82
+ }
83
+ );
84
+ }
85
+ });