kupos-ui-components-lib 9.7.0 → 9.7.2

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.
Files changed (26) hide show
  1. package/dist/KuposUIComponent.d.ts +3 -0
  2. package/dist/assets/images/anims/service_list/users_anim.json +1 -0
  3. package/dist/components/ServiceItem/ServiceItemDesktop.d.ts +1 -1
  4. package/dist/components/ServiceItem/ServiceItemDesktop.js +10 -2
  5. package/dist/components/ServiceItem/ServiceItemMobile.d.ts +1 -1
  6. package/dist/components/ServiceItem/ServiceItemMobile.js +16 -7
  7. package/dist/components/ServiceItem/mobileTypes.d.ts +27 -0
  8. package/dist/components/ServiceItem/types.d.ts +10 -0
  9. package/dist/styles.css +62 -14
  10. package/dist/ui/FeaturServiceUiMobile/FeatureServiceUiMobile.d.ts +18 -0
  11. package/dist/ui/FeaturServiceUiMobile/FeatureServiceUiMobile.js +233 -0
  12. package/dist/ui/FeatureServiceUI/FeatureServiceUi.d.ts +7 -2
  13. package/dist/ui/FeatureServiceUI/FeatureServiceUi.js +109 -92
  14. package/dist/ui/SeatSection/SeatSection.js +1 -1
  15. package/dist/utils/CommonService.js +11 -1
  16. package/package.json +1 -1
  17. package/src/KuposUIComponent.tsx +3 -0
  18. package/src/assets/images/anims/service_list/users_anim.json +1 -0
  19. package/src/components/ServiceItem/ServiceItemDesktop.tsx +23 -1
  20. package/src/components/ServiceItem/ServiceItemMobile.tsx +331 -286
  21. package/src/components/ServiceItem/mobileTypes.ts +22 -0
  22. package/src/components/ServiceItem/types.ts +11 -1
  23. package/src/ui/FeaturServiceUiMobile/FeatureServiceUiMobile.tsx +790 -0
  24. package/src/ui/FeatureServiceUI/FeatureServiceUi.tsx +181 -253
  25. package/src/ui/SeatSection/SeatSection.tsx +1 -1
  26. package/src/utils/CommonService.ts +13 -1
@@ -23,206 +23,68 @@ const HARDCODED_OPERATORS = [
23
23
  },
24
24
  ];
25
25
 
26
- const HARDCODED_COUNTDOWN = "09:55";
27
-
28
- const HOW_IT_WORKS_STEPS = [
29
- {
30
- icon: "flexible",
31
- name: "1. Salida flexible",
32
- text: "Viajas en un horario entre las 07:00 y las 10:00 AM del día elegido.",
33
- },
34
- {
35
- icon: "bus",
36
- name: "2. Empresa asignada",
37
- text: "Una de las empresas disponibles confirma tu viaje una vez pagado.",
38
- },
39
- {
40
- icon: "price",
41
- name: "3. Precio garantizado",
42
- text: "Al seleccionar este servicio aseguras el precio reducido.",
43
- },
44
- {
45
- icon: "ticket",
46
- name: "4. ¡Listo!",
47
- text: "Recibe todos los detalles de tu viaje al instante tras la compra.",
48
- },
49
- ];
50
-
51
- const FeatureStepIcon = ({ icon }) => {
52
- const iconClassName = "h-[30px] w-[30px] text-[#171717]";
53
-
54
- switch (icon) {
55
- case "flexible":
56
- return (
57
- <svg
58
- className={iconClassName}
59
- viewBox="0 0 40 40"
60
- fill="none"
61
- aria-hidden="true"
62
- >
63
- <path
64
- d="M31.5 13.4A13 13 0 0 0 8 12.3"
65
- stroke="currentColor"
66
- strokeWidth="2.2"
67
- strokeLinecap="round"
68
- />
69
- <path
70
- d="m7.4 7.7.2 5.2 5.1-1.2M8.5 26.6A13 13 0 0 0 32 27.7"
71
- stroke="currentColor"
72
- strokeWidth="2.2"
73
- strokeLinecap="round"
74
- strokeLinejoin="round"
75
- />
76
- <path
77
- d="m32.6 32.3-.2-5.2-5.1 1.2"
78
- stroke="currentColor"
79
- strokeWidth="2.2"
80
- strokeLinecap="round"
81
- strokeLinejoin="round"
82
- />
83
- </svg>
84
- );
85
- case "bus":
86
- return (
87
- <svg
88
- className={iconClassName}
89
- viewBox="0 0 40 40"
90
- fill="none"
91
- aria-hidden="true"
92
- >
93
- <rect
94
- x="8"
95
- y="4.5"
96
- width="24"
97
- height="27"
98
- rx="2.5"
99
- stroke="currentColor"
100
- strokeWidth="2.2"
101
- />
102
- <path
103
- d="M8 18.5h24M12 9h16M12 26h4m8 0h4M11 31.5v3h5v-3m8 0v3h5v-3M5.5 10v7m29-7v7"
104
- stroke="currentColor"
105
- strokeWidth="2.2"
106
- strokeLinecap="round"
107
- />
108
- </svg>
109
- );
110
- case "price":
111
- return (
112
- <svg
113
- className={iconClassName}
114
- viewBox="0 0 40 40"
115
- fill="none"
116
- aria-hidden="true"
117
- >
118
- <circle
119
- cx="20"
120
- cy="20"
121
- r="14"
122
- stroke="currentColor"
123
- strokeWidth="2.2"
124
- />
125
- <path
126
- d="M23.7 15.4c-1-.7-2.2-1-3.6-1-2.2 0-3.8 1.1-3.8 2.8 0 4.2 7.4 1.8 7.4 5.8 0 1.8-1.6 2.8-4 2.8-1.5 0-2.9-.4-4-1.2M20 11.7v2.5m0 11.8v2.4"
127
- stroke="currentColor"
128
- strokeWidth="2.2"
129
- strokeLinecap="round"
130
- />
131
- </svg>
132
- );
133
- default:
134
- return (
135
- <svg
136
- className={iconClassName}
137
- viewBox="0 0 40 40"
138
- fill="none"
139
- aria-hidden="true"
140
- >
141
- <path
142
- d="M7 11h26v6a3.5 3.5 0 0 0 0 7v6H7v-6a3.5 3.5 0 0 0 0-7v-6Z"
143
- stroke="currentColor"
144
- strokeWidth="2.2"
145
- strokeLinejoin="round"
146
- />
147
- <path
148
- d="M22 12.5v3m0 3v3m0 3v4"
149
- stroke="currentColor"
150
- strokeWidth="2.2"
151
- strokeLinecap="round"
152
- />
153
- </svg>
154
- );
155
- }
156
- };
157
-
158
- const AssuranceIcon = ({ type }) => {
159
- const iconClassName = "h-[18px] w-[18px] shrink-0 text-white";
160
-
161
- if (type === "pending") {
162
- return (
163
- <svg
164
- className={iconClassName}
165
- viewBox="0 0 20 20"
166
- fill="none"
167
- aria-hidden="true"
168
- >
169
- <path
170
- d="M4.2 8.2a6.2 6.2 0 1 1 .5 5.1"
171
- stroke="currentColor"
172
- strokeWidth="1.8"
173
- strokeLinecap="round"
174
- />
175
- <path
176
- d="M4.2 4.8v3.4h3.4"
177
- stroke="currentColor"
178
- strokeWidth="1.8"
179
- strokeLinecap="round"
180
- strokeLinejoin="round"
181
- />
182
- </svg>
183
- );
184
- }
185
-
186
- return (
187
- <svg
188
- className={iconClassName}
189
- viewBox="0 0 20 20"
190
- fill="none"
191
- aria-hidden="true"
192
- >
193
- <path
194
- d="M10 2.2 16.3 4v5c0 4-2.4 6.8-6.3 8.7C6.1 15.8 3.7 13 3.7 9V4L10 2.2Z"
195
- stroke="currentColor"
196
- strokeWidth="1.6"
197
- strokeLinejoin="round"
198
- />
199
- <path
200
- d="m6.9 9.7 2.1 2.1 4.3-4.4"
201
- stroke="currentColor"
202
- strokeWidth="1.7"
203
- strokeLinecap="round"
204
- strokeLinejoin="round"
205
- />
206
- </svg>
207
- );
208
- };
209
-
210
26
  const FeatureServiceUi = ({
211
27
  serviceItem,
212
28
  showTopLabel,
213
- colors,
214
29
  isSoldOut,
215
30
  getAnimationIcon,
216
31
  cityOrigin,
217
32
  cityDestination,
218
33
  renderIcon,
219
34
  viewersConfig,
35
+ isFeatureDropDownExpand,
36
+ onToggleExpand,
37
+ ticketQuantity = 1,
38
+ onIncreaseTicketQuantity,
39
+ onDecreaseTicketQuantity,
40
+ onBookButtonPress,
220
41
  }) => {
221
42
  const operators =
222
43
  serviceItem?.operators?.length > 0
223
44
  ? serviceItem.operators
224
45
  : HARDCODED_OPERATORS;
225
46
 
47
+ const isItemExpanded =
48
+ serviceItem.id === isFeatureDropDownExpand ||
49
+ isFeatureDropDownExpand === true;
50
+ const canDecreaseTicketQuantity = ticketQuantity > 1;
51
+
52
+ const HOW_IT_WORKS_STEPS = [
53
+ {
54
+ icon: "flexible",
55
+ name: "1. Salida flexible",
56
+ text: "Viajas en un horario entre las 07:00 y las 10:00 AM del día elegido.",
57
+ },
58
+ {
59
+ icon: "bus",
60
+ name: "2. Empresa asignada",
61
+ text: "Una de las empresas disponibles confirma tu viaje una vez pagado.",
62
+ },
63
+ {
64
+ icon: "price",
65
+ name: "3. Precio garantizado",
66
+ text: "Al seleccionar este servicio aseguras el precio reducido.",
67
+ },
68
+ {
69
+ icon: "ticket",
70
+ name: "4. ¡Listo!",
71
+ text: "Recibe todos los detalles de tu viaje al instante tras la compra.",
72
+ },
73
+ ];
74
+
75
+ const FeatureStepIcon = ({ icon }) => {
76
+ switch (icon) {
77
+ case "flexible":
78
+ return renderIcon("flexibleIcon", "24px");
79
+ case "bus":
80
+ return renderIcon("empressaIcon", "24px");
81
+ case "price":
82
+ return renderIcon("precioIcon", "24px");
83
+ default:
84
+ return renderIcon("listoIcon", "24px");
85
+ }
86
+ };
87
+
226
88
  return (
227
89
  <div
228
90
  // ${
@@ -255,6 +117,8 @@ const FeatureServiceUi = ({
255
117
  padding: "1px 8px",
256
118
  borderRadius: "4px",
257
119
  color: "#fff",
120
+ animation: "pulse-zoom 2s ease-in-out infinite",
121
+ whiteSpace: "nowrap",
258
122
  }}
259
123
  >
260
124
  <span>AHORRAS 60%</span>
@@ -271,6 +135,7 @@ const FeatureServiceUi = ({
271
135
  fontVariantNumeric: "tabular-nums",
272
136
  display: "inline-block",
273
137
  color: "#FF5C60",
138
+ minWidth: "40px",
274
139
  }}
275
140
  />
276
141
  </span>
@@ -280,9 +145,9 @@ const FeatureServiceUi = ({
280
145
  id={`service-card-${serviceItem.id}`}
281
146
  className="bg-[#0C1421] text-white mx-auto relative rounded-[14px] p-[14px] text-[13.33px]"
282
147
  >
283
- <div className="grid grid-cols-[1.3fr_2fr_1.2fr] gap-[16px] items-stretch">
148
+ <div className="grid grid-cols-[23%_50%_27%] items-stretch">
284
149
  {/* LEFT: origin, destination, flexible, time, confirmed seat */}
285
- <div className="flex flex-col justify-between gap-[20px] py-[2px] ">
150
+ <div className="flex flex-col justify-between gap-[20px] my-[14px] pr-[22px]">
286
151
  <div className="flex flex-col gap-[8px]">
287
152
  <div className="flex items-center gap-[8px]">
288
153
  <img
@@ -300,7 +165,7 @@ const FeatureServiceUi = ({
300
165
  <img
301
166
  src={serviceItem.icons?.whiteDestination}
302
167
  alt="destination"
303
- className={`w-[16px] h-[16px] shrink-0 ${
168
+ className={`w-[14px] h-[14px] shrink-0 ${
304
169
  isSoldOut ? "grayscale" : ""
305
170
  }`}
306
171
  style={{ opacity: isSoldOut ? 0.5 : 1 }}
@@ -312,23 +177,6 @@ const FeatureServiceUi = ({
312
177
  </div>
313
178
 
314
179
  <div className="flex flex-col gap-[8px]">
315
- {/* Salida flexible badge — uses flexibleIcon */}
316
- {/* <div
317
- className="flex items-center gap-[6px] rounded-[8px] px-[8px] py-[4px] w-fit mb-[6px]"
318
- style={{
319
- border: "1px solid #363c48",
320
- backgroundColor: "#1a202e",
321
- }}
322
- >
323
- <img
324
- src={serviceItem.icons?.busIcon}
325
- alt="bus"
326
- style={{ width: "20px", height: "20px" }}
327
- />
328
- <span className="text-[12px] whitespace-nowrap">
329
- Salida flexible
330
- </span>
331
- </div> */}
332
180
  <div className="text-[12px] bold-text whitespace-nowrap">
333
181
  Entre 07:00 AM y 10:00 AM
334
182
  </div>
@@ -336,8 +184,8 @@ const FeatureServiceUi = ({
336
184
  </div>
337
185
 
338
186
  <div className="flex flex-col items-start gap-[10px] text-[12px] ">
339
- <div className="flex items-center gap-[8px]">
340
- <AssuranceIcon type="pending" />
187
+ <div className="flex items-justify gap-[8px]">
188
+ {renderIcon("sheildIcon", "16px")}
341
189
 
342
190
  <span
343
191
  className="text-[10px]"
@@ -348,8 +196,8 @@ const FeatureServiceUi = ({
348
196
  Empresa y hora a confirmar luego del pago.
349
197
  </span>
350
198
  </div>
351
- <div className="flex items-center gap-[8px]">
352
- <AssuranceIcon type="secured" />
199
+ <div className="flex items-justify gap-[8px]">
200
+ {renderIcon("confirmarIcon", "16px")}
353
201
 
354
202
  <span
355
203
  className="text-[10px]"
@@ -364,7 +212,7 @@ const FeatureServiceUi = ({
364
212
  </div>
365
213
 
366
214
  {/* MIDDLE: competing operators + viewers */}
367
- <div className="px-[16px] flex flex-col items-center justify-between gap-[12px] py-[2px] border-r border-[#363c48] border-l border-[#363c48]">
215
+ <div className="min-w-0 px-[22px] flex flex-col items-center justify-between gap-[16px] py-[2px] border-r border-[#363c48] border-l border-[#363c48]">
368
216
  <div className="text-center">
369
217
  <div className="bold-text text-[14px]">
370
218
  3 operadores compitiendo por tu compra
@@ -374,30 +222,29 @@ const FeatureServiceUi = ({
374
222
  </div> */}
375
223
  </div>
376
224
 
377
- <div className="flex items-stretch justify-center gap-[8px] w-full mb-[16px]">
225
+ <div className="grid w-full grid-cols-3 items-stretch gap-[14px] mb-[12px]">
378
226
  {operators.map((op, idx) => (
379
227
  <div
380
228
  key={idx}
381
- className="flex flex-col items-center justify-center gap-[8px] rounded-[8px]"
229
+ className="flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]"
382
230
  style={{
383
- width: "140px",
384
231
  // height: "80px",
385
232
  border: "1px solid #363c48",
386
233
  backgroundColor: "#1a202e",
387
- padding: "14px",
234
+ padding: "14px 10px",
388
235
  }}
389
236
  >
390
237
  <img
391
238
  src={serviceItem.operator_details[0]}
392
239
  alt={op.name}
393
- className={`h-[24px] w-auto object-contain ${
240
+ className={`h-[24px] max-w-full object-contain ${
394
241
  isSoldOut ? "grayscale" : ""
395
242
  }`}
396
243
  />
397
244
  <span className="text-[11px] truncate max-w-full text-center">
398
245
  {serviceItem.operator_details[2]}
399
246
  </span>
400
- <div className="bg-[#FF8F45] text-white text-[12px] font-bold px-[16px] py-[4px] rounded-[4px] bold-text">
247
+ <div className="bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap">
401
248
  <span>{op?.time}</span>
402
249
  </div>
403
250
  <span className="text-[10px] mt-[6px]">
@@ -408,19 +255,19 @@ const FeatureServiceUi = ({
408
255
  </div>
409
256
 
410
257
  <div
411
- className="flex items-center justify-center gap-[6px] text-[12px]"
258
+ className="flex w-full items-center justify-center gap-[6px] text-[12px]"
412
259
  style={{
413
260
  border: "1px solid #363c48",
414
261
  backgroundColor: "#1a202e",
415
262
  padding: "8px 14px",
416
263
  borderRadius: "24px",
417
- width: "430px",
418
264
  }}
419
265
  >
420
- <img
421
- src={serviceItem.icons?.userIcon}
422
- alt="eye"
423
- style={{ width: "16px", height: "16px" }}
266
+ <LottiePlayer
267
+ // animationData={serviceItem.icons.flexibleAnim}
268
+ animationData={getAnimationIcon("usersAnimation")}
269
+ width="18px"
270
+ height="18px"
424
271
  />
425
272
  <span>
426
273
  <span className="bold-text text-white">
@@ -435,15 +282,8 @@ const FeatureServiceUi = ({
435
282
  color: "#FF5C60",
436
283
  }}
437
284
  />{" "}
438
- <span
439
- style={{
440
- color: "#FF5C60",
441
- }}
442
- >
443
- personas
444
- </span>
445
285
  </span>{" "}
446
- viendo este viaje |{" "}
286
+ viendo |{" "}
447
287
  <span
448
288
  className="bold-text"
449
289
  ref={(node) =>
@@ -457,28 +297,98 @@ const FeatureServiceUi = ({
457
297
  </div>
458
298
 
459
299
  {/* RIGHT: price + button */}
460
- <div className="flex flex-col justify-center gap-[12px] py-[2px] relative mb-[16px]">
300
+ <div className="flex flex-col justify-center gap-[12px] py-[2px] pl-[22px] pr-[10px] relative mb-[16px]">
461
301
  <div
462
302
  className="flex flex-col gap-[6px] "
463
303
  style={{
464
304
  alignItems: "center",
465
305
  }}
466
306
  >
467
- <span className="text-[#FF8F45] bold-text text-[26px] leading-tight">
307
+ <span
308
+ className="text-[#FF8F45] bold-text text-[26px] leading-tight"
309
+ style={{
310
+ animation: "pulse-zoom 2s ease-in-out infinite",
311
+ whiteSpace: "nowrap",
312
+ }}
313
+ >
468
314
  60% OFF
469
315
  </span>
470
- <span className="text-[#666] text-[14px] line-through">
316
+ {/* <span className="text-[#666] text-[14px] line-through">
317
+ $10.000
318
+ </span> */}
319
+ <span
320
+ className="text-[13.33px] font-normal leading-[20px] text-[#9f9f9f] relative"
321
+ style={{ position: "relative" }}
322
+ >
471
323
  $10.000
324
+ <span
325
+ style={{
326
+ position: "absolute",
327
+ left: "-2px",
328
+ top: "50%",
329
+ width: "calc(100% + 4px)",
330
+ height: "1px",
331
+
332
+ backgroundColor: "#FF5C60",
333
+
334
+ transform: "rotate(-10deg)",
335
+ transformOrigin: "center",
336
+ }}
337
+ />
472
338
  </span>
473
339
  <span className="text-white bold-text text-[28px] leading-none">
474
- $4.000
340
+ {`$${(4000 * ticketQuantity).toLocaleString()}`}
475
341
  </span>
476
342
  </div>
477
343
 
344
+ <div className="mt-[4px] flex flex-col items-center gap-[8px]">
345
+ <span className="text-[12px] text-white">
346
+ ¿Cuántos pasajes quieres?
347
+ </span>
348
+ <div
349
+ className="flex w-full items-center justify-between"
350
+ style={{
351
+ border: "1px solid #363c48",
352
+ backgroundColor: "#1a202e",
353
+ padding: "6px 14px",
354
+ borderRadius: "14px",
355
+ }}
356
+ >
357
+ <button
358
+ type="button"
359
+ aria-label="Disminuir pasajes"
360
+ disabled={!canDecreaseTicketQuantity}
361
+ onClick={() => onDecreaseTicketQuantity?.(serviceItem)}
362
+ className={`flex h-[34px] w-[34px] items-center justify-center rounded-full border-none text-[25px] leading-none text-white ${
363
+ canDecreaseTicketQuantity
364
+ ? "cursor-pointer bg-[#2d374d]"
365
+ : "cursor-not-allowed bg-[#222b3d] opacity-50"
366
+ }`}
367
+ >
368
+ -
369
+ </button>
370
+ <span className="bold-text text-[20px] text-white">
371
+ {ticketQuantity}
372
+ </span>
373
+ <button
374
+ type="button"
375
+ aria-label="Aumentar pasajes"
376
+ onClick={() => onIncreaseTicketQuantity?.(serviceItem)}
377
+ className="flex h-[34px] w-[34px] cursor-pointer items-center justify-center rounded-full border-none bg-[#2d374d] text-[25px] leading-none text-white"
378
+ >
379
+ +
380
+ </button>
381
+ </div>
382
+ </div>
383
+
478
384
  <button
479
- className="flex items-center gap-[6px] px-[20px] py-[10px] rounded-[12px] text-white bold-text text-[13px] mt-[4px] justify-center border-none cursor-pointer"
385
+ type="button"
386
+ onClick={onBookButtonPress}
387
+ className="flex items-center gap-[6px] px-[20px] py-[10px] rounded-[16px] text-white bold-text text-[13px] mt-[4px] justify-center border-none cursor-pointer"
480
388
  style={{
481
389
  backgroundColor: "#FF5C60",
390
+ animation: "pulse-zoom 2s ease-in-out infinite",
391
+ whiteSpace: "nowrap",
482
392
  }}
483
393
  >
484
394
  <LottiePlayer
@@ -491,7 +401,10 @@ const FeatureServiceUi = ({
491
401
  </button>
492
402
  </div>
493
403
 
494
- <div className="absolute bottom-[11px] right-[18px]">
404
+ <div
405
+ className={`absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`}
406
+ onClick={onToggleExpand}
407
+ >
495
408
  <img
496
409
  src={serviceItem.icons?.downArrow}
497
410
  alt="down arrow"
@@ -504,24 +417,39 @@ const FeatureServiceUi = ({
504
417
  </div>
505
418
  </div>
506
419
  </div>
507
- <div className="px-[16px] pt-[14px] pb-[6px] text-[13.33px]">
508
- <span className="bold-text">¿Cómo funciona?</span>
420
+ <div
421
+ className="grid"
422
+ style={{
423
+ gridTemplateRows: isItemExpanded ? "1fr" : "0fr",
424
+ opacity: isItemExpanded ? 1 : 0,
425
+ transition:
426
+ "grid-template-rows 300ms ease-in-out, opacity 250ms ease-in-out",
427
+ }}
428
+ >
429
+ <div
430
+ className={`min-h-0 overflow-hidden px-[16px] text-[13.33px] ${
431
+ isItemExpanded ? "pt-[14px] pb-[6px]" : "py-0"
432
+ }`}
433
+ style={{ transition: "padding 300ms ease-in-out" }}
434
+ >
435
+ <span className="bold-text">¿Cómo funciona?</span>
509
436
 
510
- <div className="mt-[14px] grid grid-cols-4 gap-[20px] px-[16px] ">
511
- {HOW_IT_WORKS_STEPS.map((step) => (
512
- <div
513
- key={step.name}
514
- className="flex flex-col items-center text-center text-[#272727]"
515
- >
516
- <FeatureStepIcon icon={step.icon} />
517
- <span className="bold-text mt-[10px] text-[12px] leading-[14px]">
518
- {step.name}
519
- </span>
520
- <span className="mt-[2px] max-w-[220px] text-[12px] leading-[14px] text-[#4a4a4a]">
521
- {step.text}
522
- </span>
523
- </div>
524
- ))}
437
+ <div className="mt-[14px] grid grid-cols-4 gap-[20px] px-[16px] ">
438
+ {HOW_IT_WORKS_STEPS.map((step) => (
439
+ <div
440
+ key={step.name}
441
+ className="flex flex-col items-center text-center text-[#272727]"
442
+ >
443
+ <FeatureStepIcon icon={step.icon} />
444
+ <span className="bold-text mt-[10px] text-[12px] leading-[14px]">
445
+ {step.name}
446
+ </span>
447
+ <span className="mt-[2px] max-w-[220px] text-[12px] leading-[14px] text-[#4a4a4a]">
448
+ {step.text}
449
+ </span>
450
+ </div>
451
+ ))}
452
+ </div>
525
453
  </div>
526
454
  </div>
527
455
  </div>
@@ -477,7 +477,7 @@ function SeatSection({
477
477
  className="absolute"
478
478
  style={{
479
479
  left: isPeru ? "-1px" : "-18px",
480
- bottom:"1px"
480
+ bottom: "1px",
481
481
  }}
482
482
  >
483
483
  {renderIcon("fireIcon", "16px")}
@@ -329,10 +329,15 @@ const commonService = {
329
329
  ) => {
330
330
  if (!node || !viewersConfig) return;
331
331
 
332
+ const { min, max, interval = 5000 } = viewersConfig;
333
+ const configKey = `${min}-${max}-${interval}`;
334
+ if (node.dataset.viewerId && node.dataset.viewerConfig === configKey) {
335
+ return;
336
+ }
337
+
332
338
  const prevId = node.dataset.viewerId;
333
339
  if (prevId) clearInterval(Number(prevId));
334
340
 
335
- const { min, max, interval = 5000 } = viewersConfig;
336
341
  const clamp = (v: number) => Math.min(max, Math.max(min, v));
337
342
  const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
338
343
 
@@ -347,6 +352,7 @@ const commonService = {
347
352
  }, interval);
348
353
 
349
354
  node.dataset.viewerId = String(id);
355
+ node.dataset.viewerConfig = configKey;
350
356
  },
351
357
 
352
358
  startCountdown: (
@@ -388,6 +394,11 @@ const commonService = {
388
394
  ) => {
389
395
  if (!node) return;
390
396
 
397
+ const configKey = `${min}-${max}`;
398
+ if (node.dataset.comprandoId && node.dataset.comprandoConfig === configKey) {
399
+ return;
400
+ }
401
+
391
402
  const prevId = node.dataset.comprandoId;
392
403
  if (prevId) clearInterval(Number(prevId));
393
404
 
@@ -408,6 +419,7 @@ const commonService = {
408
419
  }, 5000); // Update every 5 seconds
409
420
 
410
421
  node.dataset.comprandoId = String(id);
422
+ node.dataset.comprandoConfig = configKey;
411
423
  },
412
424
  };
413
425