kupos-ui-components-lib 9.11.9 → 10.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.
@@ -461,12 +461,12 @@ function PeruServiceItemDesktop({ serviceItem, onBookButtonPress, colors, metaDa
461
461
  React.createElement(SeatSection, { seatTypes: serviceItem.seat_types, serviceItem: serviceItem, availableSeats: serviceItem.available_seats, isSoldOut: isSoldOut, priceColor: colors.priceColor, currencySign: currencySign, removeDuplicateSeats: removeDuplicateSeats, isPeru: isPeru, renderIcon: renderIcon, discountSeatPriceColor: colors.discountSeatPriceColor })),
462
462
  React.createElement("div", { className: "relative" },
463
463
  React.createElement(KuposButton, { isSoldOut: isSoldOut, isLoading: serviceDetailsLoading, buttonColor: colors.kuposButtonColor, buyLabel: translation === null || translation === void 0 ? void 0 : translation.buyButton, soldOutLabel: translation === null || translation === void 0 ? void 0 : translation.soldOutButton, soldOutIcon: renderIcon("soldOutIcon", "14px"), onClick: checkMidnight }),
464
- showLastSeats ? (React.createElement("div", { className: "flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[40px]" }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) < 10 &&
465
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) > 0 && (React.createElement("div", { className: "text-[12px] mt-1 text-center", style: {
464
+ showLastSeats ? (React.createElement("div", { className: "flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[36px]" }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) < 10 &&
465
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) > 0 && (React.createElement("div", { className: "text-[11px] mt-1 text-center whitespace-nowrap", style: {
466
466
  color: colors.seatPriceColor,
467
467
  } }, "\u00A1\u00DAltimos Asientos!")))) : null)),
468
468
  React.createElement(BottomAmenities, { otherItems: otherItems, serviceItem: serviceItem, grayscaleClass: grayscaleClass, isSoldOut: isSoldOut, isItemExpanded: isItemExpanded, colors: colors, translation: translation, getAnimationIcon: getAnimationIcon, downArrowIcon: renderIcon("downArrow", "10px"), onToggleExpand: () => setIsExpand && setIsExpand(isItemExpanded ? null : serviceItem.id), isPeru: isPeru }))),
469
469
  children,
470
- React.createElement(ServiceBadges, { showTopLabel: showTopLabel, isSoldOut: isSoldOut, colors: colors, renderIcon: renderIcon, translation: translation, serviceItem: serviceItem })));
470
+ React.createElement(ServiceBadges, { showTopLabel: showTopLabel, isSoldOut: isSoldOut, colors: colors, renderIcon: renderIcon, translation: translation, serviceItem: serviceItem, isAllinBus: isAllinBus, boardingName: boardingName, getAnimationIcon: getAnimationIcon })));
471
471
  }
472
472
  export default PeruServiceItemDesktop;
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import { MobileServiceItemProps } from "./mobileTypes";
3
- declare function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, }: MobileServiceItemProps): React.ReactElement;
3
+ declare function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, isAllinBus, }: MobileServiceItemProps): React.ReactElement;
4
4
  export default ServiceItemMobile;
@@ -18,8 +18,8 @@ const exceptions = [
18
18
  "blanco",
19
19
  "asiento_mascota",
20
20
  ];
21
- function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, }) {
22
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
21
+ function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, orignLabel, destinationLabel, amenitiesData, setShowDropdown, showDropdown, isExpanded, setIsExpanded, setAmenetiesAtomValue, isCiva, currencySign, isPeru, showRating, showLastSeats, removeDuplicateSeats, isLinatal, viewersConfig, operatorLabel, isTrain, isFeatureDropDownExpand, setIsFeatureDropDownExpand, ticketQuantity, onIncreaseTicketQuantity, onDecreaseTicketQuantity, cityOrigin, cityDestination, isNewUi, onRemateUiButtonClick, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData, isFlores, isAllinBus, }) {
22
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
23
23
  const isItemExpanded = serviceItem.id === isExpanded;
24
24
  const isPetSeat = (Object.keys(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.pet_seat_info) || []).length > 0;
25
25
  const isNewUiEnabled = ((_b = (_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discounts) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.new_ui_enabled) === true;
@@ -89,6 +89,17 @@ function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, o
89
89
  if (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_transpordo) {
90
90
  isConexion = true;
91
91
  }
92
+ const extractStage = (stageArr, index) => {
93
+ const stageObj = stageArr[index];
94
+ const key = Object.keys(stageObj)[0];
95
+ return stageObj[key][0].split(", ")[0];
96
+ };
97
+ const boardingName = ((_e = serviceItem.stage_details_arr) === null || _e === void 0 ? void 0 : _e.length)
98
+ ? extractStage(serviceItem.stage_details_arr, 0)
99
+ : null;
100
+ const dropoffName = ((_f = serviceItem.stage_details_arr) === null || _f === void 0 ? void 0 : _f.length)
101
+ ? extractStage(serviceItem.stage_details_arr, 1)
102
+ : null;
92
103
  return (React.createElement(React.Fragment, null, isNewUi ? (React.createElement(FeatureServiceUiMobile, { serviceItem: serviceItem, showTopLabel: showTopLabel, colors: colors, isSoldOut: isSoldOut, cityOrigin: cityOrigin, cityDestination: cityDestination, renderIcon: renderIcon, viewersConfig: viewersConfig, isFeatureDropDownExpand: isFeatureDropDownExpand, ticketQuantity: ticketQuantity, onIncreaseTicketQuantity: onIncreaseTicketQuantity, onDecreaseTicketQuantity: onDecreaseTicketQuantity, onBookButtonPress: onRemateUiButtonClick, onToggleExpand: () => setIsFeatureDropDownExpand &&
93
104
  setIsFeatureDropDownExpand(isFeatureDropDownExpand === serviceItem.id ||
94
105
  isFeatureDropDownExpand === true
@@ -96,7 +107,8 @@ function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, o
96
107
  : serviceItem.id), selectedTimeSlot: selectedTimeSlot, onTimeSlotChange: onTimeSlotChange, isTimeDropdownOpen: isTimeDropdownOpen, onTimeDropdownToggle: onTimeDropdownToggle, wowDealData: wowDealData })) : (React.createElement("div", { className: `relative ${!serviceItem.offer_text || !(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) ? "mb-[14px]" : showTopLabel || (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ? "mb-[20px]" : "mb-[12px]"} ${(serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) ||
97
108
  isConexion ||
98
109
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.train_type_label) === "Tren Express (Nuevo)" ||
99
- showTopLabel
110
+ showTopLabel ||
111
+ boardingName
100
112
  ? "mt-[20px]"
101
113
  : "mt-[10px]"} `, style: { zIndex: 1 } },
102
114
  React.createElement("div", { className: `z-1 ${((hasOfferText && isNewUiEnabled) ||
@@ -125,17 +137,17 @@ function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, o
125
137
  color: isSoldOut ? "#bbb" : "text-[#464647]",
126
138
  } },
127
139
  React.createElement("span", { className: "ml-[3px] min-[420]:text-[13px] text-[12px] text-ellipsis overflow-hidden whitespace-nowrap max-w-[120px]" }, serviceItem.operator_details[2]))))) : null)),
128
- React.createElement(DateTimeSectionMobile, { onBookButtonPress: onBookButtonPress, isCiva: isCiva, isSoldOut: isSoldOut, isLinatal: isLinatal, isPeru: isPeru, orignLabel: orignLabel, destinationLabel: destinationLabel, originIcon: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.origin, destinationIcon: (_f = serviceItem.icons) === null || _f === void 0 ? void 0 : _f.destination, travelDate: serviceItem.travel_date, arrivalDate: serviceItem.arrival_date, depTime: serviceItem.dep_time, arrTime: serviceItem.arr_time, seatTypes: serviceItem.seat_types, seatPriceColor: colors.seatPriceColor, tooltipBgColor: colors.tooltipBgColor, currencySign: currencySign, availableSeats: serviceItem.available_seats, removeDuplicateSeats: removeDuplicateSeats, serviceItem: serviceItem, showLastSeats: showLastSeats, discountSeatPriceColor: colors.discountSeatPriceColor }),
140
+ React.createElement(DateTimeSectionMobile, { onBookButtonPress: onBookButtonPress, isCiva: isCiva, isSoldOut: isSoldOut, isLinatal: isLinatal, isPeru: isPeru, orignLabel: orignLabel, destinationLabel: destinationLabel, originIcon: (_g = serviceItem.icons) === null || _g === void 0 ? void 0 : _g.origin, destinationIcon: (_h = serviceItem.icons) === null || _h === void 0 ? void 0 : _h.destination, travelDate: serviceItem.travel_date, arrivalDate: serviceItem.arrival_date, depTime: serviceItem.dep_time, arrTime: serviceItem.arr_time, seatTypes: serviceItem.seat_types, seatPriceColor: colors.seatPriceColor, tooltipBgColor: colors.tooltipBgColor, currencySign: currencySign, availableSeats: serviceItem.available_seats, removeDuplicateSeats: removeDuplicateSeats, serviceItem: serviceItem, showLastSeats: showLastSeats, discountSeatPriceColor: colors.discountSeatPriceColor }),
129
141
  hasDiscount && (React.createElement("div", { className: "flex justify-end" }, isSoldOut ? (React.createElement("span", { className: "col-span-2 min-[420]:text-[13px] text-right text-[12px] text-[#ccc]" }, "Agotado")) : null)),
130
142
  showLastSeats ? (React.createElement("div", { className: "flex justify-end " }, (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) < 10 &&
131
143
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.available_seats) > 0 && (React.createElement("div", { className: "text-[10px] text-center", style: {
132
144
  color: colors.tooltipBgColor,
133
145
  } }, "\u00A1\u00DAltimos Asientos!")))) : null,
134
146
  React.createElement("div", { className: "bg-[#E6E6E6] mt-[10px] mb-[8px] h-[1px]" }),
135
- React.createElement(BottomAmenitiesMobile, { isSoldOut: isSoldOut, amenitiesNodes: amenities(), hoursIcon: renderIcon("hours", "14px"), duration: (_g = serviceItem.duration) === null || _g === void 0 ? void 0 : _g.toString(), isDirectTrip: serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip, directoColor: colors.directoColor, directoAnim: serviceItem.icons.directoAnim, isChangeTicket: serviceItem.is_change_ticket, isPetSeat: isPetSeat, petSeatInfo: serviceItem.pet_seat_info, petFriendlyAnim: serviceItem.icons.petFriendlyAnim, flexibleAnim: serviceItem.icons.flexibleAnim, isTrackingEnabled: serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_tracking_enabled, locationAnim: serviceItem.icons.locationAnim, downArrowIcon: serviceItem.icons.downArrow, showDropdown: isItemExpanded, setShowDropdown: () => setIsExpanded(isItemExpanded ? null : serviceItem.id), onDropdownToggle: () => {
147
+ React.createElement(BottomAmenitiesMobile, { isSoldOut: isSoldOut, amenitiesNodes: amenities(), hoursIcon: renderIcon("hours", "14px"), duration: (_j = serviceItem.duration) === null || _j === void 0 ? void 0 : _j.toString(), isDirectTrip: serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip, directoColor: colors.directoColor, directoAnim: serviceItem.icons.directoAnim, isChangeTicket: serviceItem.is_change_ticket, isPetSeat: isPetSeat, petSeatInfo: serviceItem.pet_seat_info, petFriendlyAnim: serviceItem.icons.petFriendlyAnim, flexibleAnim: serviceItem.icons.flexibleAnim, isTrackingEnabled: serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_tracking_enabled, locationAnim: serviceItem.icons.locationAnim, downArrowIcon: serviceItem.icons.downArrow, showDropdown: isItemExpanded, setShowDropdown: () => setIsExpanded(isItemExpanded ? null : serviceItem.id), onDropdownToggle: () => {
136
148
  setIsExpanded(isItemExpanded ? null : serviceItem.id);
137
149
  }, isPeru: isPeru, femaleAnim: serviceItem.icons.femaleAnim, ladiesBookedSeats: serviceItem.ladies_booked_seats, isDpEnabled: serviceItem.is_dp_enabled })),
138
- React.createElement(ServiceBadgesMobile, { showTopLabel: showTopLabel, isSoldOut: isSoldOut, colors: colors, renderIcon: renderIcon, serviceItem: serviceItem, isConexion: isConexion })),
150
+ React.createElement(ServiceBadgesMobile, { showTopLabel: showTopLabel, isSoldOut: isSoldOut, colors: colors, renderIcon: renderIcon, serviceItem: serviceItem, isConexion: isConexion, isAllinBus: isAllinBus, boardingName: boardingName })),
139
151
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) &&
140
152
  !isNewUiEnabled &&
141
153
  !isSoldOut &&
@@ -167,9 +179,9 @@ function ServiceItemMobile({ serviceItem, onBookButtonPress, colors, busStage, o
167
179
  React.createElement("div", { className: "flex flex-col gap-[8px] text-[12px] min-[420px]:text-[12px] text-[#464647]", style: { lineHeight: 1.6 } },
168
180
  React.createElement("div", { className: "flex justify-between items-center" },
169
181
  (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_dp_enabled) &&
170
- Object.keys((_h = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents) !== null && _h !== void 0 ? _h : {})
182
+ Object.keys((_k = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discount_percents) !== null && _k !== void 0 ? _k : {})
171
183
  .length === 0 &&
172
- ((_j = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) !== null && _j !== void 0 ? _j : []).length === 0 ? (React.createElement("div", { className: "flex items-center gap-[6px]" },
184
+ ((_l = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.dp_discounted_seats) !== null && _l !== void 0 ? _l : []).length === 0 ? (React.createElement("div", { className: "flex items-center gap-[6px]" },
173
185
  React.createElement(LottiePlayer, { animationData: serviceItem.icons.starAnimation, width: "14px", height: "14px" }),
174
186
  React.createElement("span", { className: "text-[#fff]" }, "M\u00E1s elegido"))) : (React.createElement("div", { className: `flex ${((serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.offer_text) || "").length > 10 ? "items-start" : "items-center"}` },
175
187
  React.createElement("div", { className: isLongOfferText ? "mt-[2px]" : "" },
@@ -158,6 +158,7 @@ export interface MobileServiceItemProps {
158
158
  [key: string]: string | Record<string, string | undefined> | undefined;
159
159
  };
160
160
  useLottieFor?: string[];
161
+ stage_details_arr?: any;
161
162
  };
162
163
  onBookButtonPress?: () => void;
163
164
  onRemateUiButtonClick?: () => void;
@@ -237,4 +238,5 @@ export interface MobileServiceItemProps {
237
238
  wowDealData?: any;
238
239
  isFlores?: boolean;
239
240
  operatorLabel?: string;
241
+ isAllinBus?: any;
240
242
  }
package/dist/styles.css CHANGED
@@ -60,6 +60,9 @@
60
60
  .top-\[30px\] {
61
61
  top: 30px;
62
62
  }
63
+ .top-\[36px\] {
64
+ top: 36px;
65
+ }
63
66
  .top-\[40px\] {
64
67
  top: 40px;
65
68
  }
@@ -1,6 +1,15 @@
1
1
  import React from "react";
2
2
  import LottiePlayer from "../../assets/LottiePlayer";
3
3
  import commonService from "../../utils/CommonService";
4
+ if (typeof document !== "undefined") {
5
+ if (!document.getElementById("feature-spin-style")) {
6
+ const style = document.createElement("style");
7
+ style.id = "feature-spin-style";
8
+ style.textContent =
9
+ "@keyframes feature-spin { to { transform: rotate(360deg); } }";
10
+ document.head.appendChild(style);
11
+ }
12
+ }
4
13
  const HARDCODED_OPERATORS = [
5
14
  {
6
15
  logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Turbus_logo.svg/320px-Turbus_logo.svg.png",
@@ -309,7 +318,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
309
318
  React.createElement("span", { style: { color: "#FF5C60" } }, "viendo"),
310
319
  " |",
311
320
  " ",
312
- React.createElement("span", { className: "bold-text", ref: (node) => commonService.startComprandoCount(node, 4, 16), style: { fontVariantNumeric: "tabular-nums" } }),
321
+ React.createElement("span", { className: "bold-text", ref: (node) => commonService.startComprandoDealCount(node, 4, 16), style: { fontVariantNumeric: "tabular-nums" } }),
313
322
  " ",
314
323
  "han comprado"))),
315
324
  React.createElement("div", { className: "flex flex-col justify-center gap-[12px] py-[2px] pl-[22px] pr-[10px] relative mb-[16px]" },
@@ -353,16 +362,42 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
353
362
  "m\u00E1x. ",
354
363
  maxSeatsPerBooking,
355
364
  " pasajes"))),
356
- React.createElement("button", { type: "button", onClick: () => onBookButtonPress === null || onBookButtonPress === void 0 ? void 0 : onBookButtonPress(ticketQuantity, serviceItem), 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 text-center", style: {
365
+ React.createElement("button", { type: "button", ref: (btn) => {
366
+ if (!btn)
367
+ return;
368
+ btn.onclick = () => {
369
+ if (btn.disabled)
370
+ return;
371
+ btn.disabled = true;
372
+ btn.style.cursor = "not-allowed";
373
+ btn.style.opacity = "0.85";
374
+ const normal = btn.querySelector('[data-btn-state="normal"]');
375
+ const loading = btn.querySelector('[data-btn-state="loading"]');
376
+ if (normal)
377
+ normal.style.display = "none";
378
+ if (loading)
379
+ loading.style.display = "flex";
380
+ onBookButtonPress === null || onBookButtonPress === void 0 ? void 0 : onBookButtonPress(ticketQuantity, serviceItem);
381
+ };
382
+ }, 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 text-center", style: {
357
383
  backgroundColor: "#FF5C60",
358
384
  whiteSpace: "nowrap",
385
+ minWidth: "120px",
359
386
  } },
360
- React.createElement(LottiePlayer
361
- // animationData={serviceItem.icons.flexibleAnim}
362
- , {
363
- // animationData={serviceItem.icons.flexibleAnim}
364
- animationData: getAnimationIcon("thunderAnimation"), width: "16px", height: "16px" }),
365
- React.createElement("span", { className: "whitespace-nowrap" }, "\u00A1Lo quiero!"))),
387
+ React.createElement("span", { "data-btn-state": "normal", style: { display: "flex", alignItems: "center", gap: "6px" } },
388
+ React.createElement(LottiePlayer, { animationData: getAnimationIcon("thunderAnimation"), width: "16px", height: "16px" }),
389
+ React.createElement("span", { className: "whitespace-nowrap" }, "\u00A1Lo quiero!")),
390
+ React.createElement("span", { "data-btn-state": "loading", style: { display: "none", alignItems: "center", gap: "6px" } },
391
+ React.createElement("span", { style: {
392
+ display: "inline-block",
393
+ width: "16px",
394
+ height: "16px",
395
+ border: "2px solid rgba(255,255,255,0.4)",
396
+ borderTopColor: "#fff",
397
+ borderRadius: "50%",
398
+ animation: "feature-spin 0.7s linear infinite",
399
+ flexShrink: 0,
400
+ } })))),
366
401
  React.createElement("div", { className: `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`, onClick: onToggleExpand },
367
402
  React.createElement("img", { src: (_f = serviceItem.icons) === null || _f === void 0 ? void 0 : _f.downArrow, alt: "down arrow", style: {
368
403
  width: "14px",
@@ -187,7 +187,8 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
187
187
  ? CommonService.truncateSeatLabel(filteredSeats[0].label)
188
188
  : filteredSeats[0].label
189
189
  : null;
190
- const operatorServiceName = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus" || "Movil Bus";
190
+ const operatorServiceName = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus" ||
191
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "Movil Bus";
191
192
  return (React.createElement(React.Fragment, null,
192
193
  hasDiscount && (React.createElement("span", { className: "text-[13.33px]", style: {
193
194
  color: "#999",
@@ -12,6 +12,9 @@ interface ServiceBadgesProps {
12
12
  is_direct_trip?: boolean;
13
13
  train_type_label?: string;
14
14
  };
15
+ isAllinBus?: boolean;
16
+ boardingName?: string;
17
+ getAnimationIcon?: (iconKey: string) => any;
15
18
  }
16
19
  declare const ServiceBadges: React.FC<ServiceBadgesProps>;
17
20
  export default ServiceBadges;
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
- const ServiceBadges = ({ showTopLabel, isSoldOut, colors, renderIcon, translation, serviceItem, }) => {
2
+ import LottiePlayer from "../../assets/LottiePlayer";
3
+ const ServiceBadges = ({ showTopLabel, isSoldOut, colors, renderIcon, translation, serviceItem, isAllinBus = false, boardingName, getAnimationIcon, }) => {
3
4
  return (React.createElement("div", { className: "absolute -top-[10px] left-0 w-full flex items-center justify-end gap-[12px] pr-[22px] z-10" },
4
5
  showTopLabel && (React.createElement("div", { className: `flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-10`, style: {
5
6
  backgroundColor: "#fff",
@@ -38,6 +39,12 @@ const ServiceBadges = ({ showTopLabel, isSoldOut, colors, renderIcon, translatio
38
39
  color: isSoldOut ? "#ccc" : colors.topLabelColor,
39
40
  } },
40
41
  renderIcon("directo", "12px"),
41
- React.createElement("div", null, "Tren Express")))));
42
+ React.createElement("div", null, "Tren Express"))),
43
+ boardingName && isAllinBus && (React.createElement("div", { className: `flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`, style: {
44
+ backgroundColor: isSoldOut ? "#ddd" : colors.ratingBottomColor,
45
+ } },
46
+ React.createElement("div", { className: isSoldOut ? "grayscale" : "" },
47
+ React.createElement(LottiePlayer, { animationData: getAnimationIcon("priorityStageAnim"), width: "14px", height: "14px" })),
48
+ React.createElement("div", { className: isSoldOut ? "text-white" : `text-[${colors.topLabelColor}]` }, boardingName)))));
42
49
  };
43
50
  export default ServiceBadges;
@@ -12,6 +12,8 @@ interface ServiceBadgesMobileProps {
12
12
  train_type_label?: string;
13
13
  };
14
14
  isConexion?: boolean;
15
+ isAllinBus?: any;
16
+ boardingName?: string;
15
17
  }
16
18
  declare const ServiceBadgesMobile: React.FC<ServiceBadgesMobileProps>;
17
19
  export default ServiceBadgesMobile;
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- const ServiceBadgesMobile = ({ showTopLabel, isSoldOut, colors, renderIcon, serviceItem, isConexion, }) => {
2
+ const ServiceBadgesMobile = ({ showTopLabel, isSoldOut, colors, renderIcon, serviceItem, isConexion, isAllinBus = false, boardingName, }) => {
3
3
  return (React.createElement("div", { className: "absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[17px] z-10" },
4
4
  showTopLabel && (React.createElement("div", { className: `flex items-center gap-[2p x] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] h-[24px] z-20`, style: {
5
5
  backgroundColor: "#fff",
@@ -38,6 +38,35 @@ const ServiceBadgesMobile = ({ showTopLabel, isSoldOut, colors, renderIcon, serv
38
38
  color: isSoldOut ? "#ccc" : colors.topLabelColor,
39
39
  } },
40
40
  renderIcon("directo", "12px"),
41
- React.createElement("div", null, "Tren Express")))));
41
+ React.createElement("div", null, "Tren Express"))),
42
+ boardingName && isAllinBus && (React.createElement("div", { className: `flex items-center gap-[2p x] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] h-[24px] z-20`, style: {
43
+ backgroundColor: "#fff",
44
+ border: isSoldOut
45
+ ? "1px solid #ccc"
46
+ : `1px solid ${colors.topLabelColor}`,
47
+ color: isSoldOut ? "#ccc" : colors.topLabelColor,
48
+ } },
49
+ React.createElement("div", { className: isSoldOut ? "grayscale" : "", style: { marginTop: "1px" } }, renderIcon("specialDeparture", "12px", { marginTop: "1px" })),
50
+ React.createElement("div", { style: {
51
+ color: isSoldOut ? "#ccc" : colors.topLabelColor,
52
+ } }, boardingName))
53
+ // <div
54
+ // className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`}
55
+ // style={{
56
+ // backgroundColor: isSoldOut ? "#ddd" : colors.ratingBottomColor,
57
+ // }}
58
+ // >
59
+ // <div className={isSoldOut ? "grayscale" : ""}>
60
+ // {renderIcon("specialDeparture", "12px", { marginTop: "1px" })}
61
+ // </div>
62
+ // <div
63
+ // className={
64
+ // isSoldOut ? "text-white" : `text-[${colors.topLabelColor}]`
65
+ // }
66
+ // >
67
+ // {boardingName}
68
+ // </div>
69
+ // </div>
70
+ )));
42
71
  };
43
72
  export default ServiceBadgesMobile;
@@ -29,6 +29,7 @@ declare const commonService: {
29
29
  startCountdown: (node: HTMLSpanElement | null, countdownSeconds?: number) => void;
30
30
  startDealCountdown: (node: HTMLSpanElement | null, countdownSeconds: number) => void;
31
31
  startComprandoCount: (node: HTMLSpanElement | null, min?: number, max?: number) => void;
32
+ startComprandoDealCount: (node: HTMLSpanElement | null, min?: number, max?: number) => void;
32
33
  timeToMinutes: (time: string) => number;
33
34
  minutesToTime: (minutes: number) => string;
34
35
  generateTimeSlots: (from: string, to: string, windowHours: number) => Array<{
@@ -411,6 +411,41 @@ const commonService = {
411
411
  node.dataset.comprandoId = String(id);
412
412
  node.dataset.comprandoConfig = configKey;
413
413
  },
414
+ startComprandoDealCount: (node, min = 4, max = 16) => {
415
+ if (!node)
416
+ return;
417
+ const configKey = `${min}-${max}`;
418
+ if (node.dataset.comprandoDealId &&
419
+ node.dataset.comprandoDealConfig === configKey) {
420
+ return;
421
+ }
422
+ const prevId = node.dataset.comprandoDealId;
423
+ if (prevId)
424
+ clearInterval(Number(prevId));
425
+ const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
426
+ node.textContent = String(initialValue);
427
+ const id = setInterval(() => {
428
+ const current = Number(node.textContent) || initialValue;
429
+ const changePercent = 0.05; // 5% change
430
+ const baseChange = Math.ceil(current * changePercent);
431
+ const rand = Math.random();
432
+ let change = baseChange;
433
+ if (baseChange <= 1) {
434
+ change = rand < 0.7 ? 1 : rand < 0.9 ? 2 : 3;
435
+ }
436
+ else {
437
+ const variation = rand < 0.4 ? 0 : rand < 0.7 ? 1 : rand < 0.9 ? 2 : 3;
438
+ change = baseChange + variation;
439
+ }
440
+ // Only increase, never decrease
441
+ let next = current + change;
442
+ // Clamp within max
443
+ next = Math.min(max, next);
444
+ node.textContent = String(next);
445
+ }, 5000); // Update every 5 seconds
446
+ node.dataset.comprandoDealId = String(id);
447
+ node.dataset.comprandoDealConfig = configKey;
448
+ },
414
449
  timeToMinutes: (time) => {
415
450
  const [h, m] = time.split(":").map(Number);
416
451
  return h * 60 + (m || 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.11.9",
3
+ "version": "10.0.0",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -906,11 +906,11 @@ function PeruServiceItemDesktop({
906
906
  onClick={checkMidnight}
907
907
  />
908
908
  {showLastSeats ? (
909
- <div className="flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[40px]">
909
+ <div className="flex justify-center mr-[11px] w-[100%] right-[0px] absolute left-[0] top-[36px]">
910
910
  {serviceItem?.available_seats < 10 &&
911
911
  serviceItem?.available_seats > 0 && (
912
912
  <div
913
- className="text-[12px] mt-1 text-center"
913
+ className="text-[11px] mt-1 text-center whitespace-nowrap"
914
914
  style={{
915
915
  color: colors.seatPriceColor,
916
916
  }}
@@ -1025,6 +1025,9 @@ function PeruServiceItemDesktop({
1025
1025
  renderIcon={renderIcon}
1026
1026
  translation={translation}
1027
1027
  serviceItem={serviceItem}
1028
+ isAllinBus={isAllinBus}
1029
+ boardingName={boardingName}
1030
+ getAnimationIcon={getAnimationIcon}
1028
1031
  />
1029
1032
 
1030
1033
  {/* <div className="absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[15px] z-10 ">
@@ -63,6 +63,7 @@ function ServiceItemMobile({
63
63
  onTimeDropdownToggle,
64
64
  wowDealData,
65
65
  isFlores,
66
+ isAllinBus,
66
67
  }: MobileServiceItemProps): React.ReactElement {
67
68
  const isItemExpanded = serviceItem.id === isExpanded;
68
69
  const isPetSeat = (Object.keys(serviceItem?.pet_seat_info) || []).length > 0;
@@ -174,6 +175,23 @@ function ServiceItemMobile({
174
175
  isConexion = true;
175
176
  }
176
177
 
178
+ const extractStage = (
179
+ stageArr: Record<string, string[]>[],
180
+ index: number,
181
+ ) => {
182
+ const stageObj = stageArr[index];
183
+ const key = Object.keys(stageObj)[0];
184
+ return stageObj[key][0].split(", ")[0];
185
+ };
186
+
187
+ const boardingName = serviceItem.stage_details_arr?.length
188
+ ? extractStage(serviceItem.stage_details_arr, 0)
189
+ : null;
190
+
191
+ const dropoffName = serviceItem.stage_details_arr?.length
192
+ ? extractStage(serviceItem.stage_details_arr, 1)
193
+ : null;
194
+
177
195
  return (
178
196
  <>
179
197
  {isNewUi ? (
@@ -212,7 +230,8 @@ function ServiceItemMobile({
212
230
  serviceItem?.is_direct_trip ||
213
231
  isConexion ||
214
232
  serviceItem?.train_type_label === "Tren Express (Nuevo)" ||
215
- showTopLabel
233
+ showTopLabel ||
234
+ boardingName
216
235
  ? "mt-[20px]"
217
236
  : "mt-[10px]"
218
237
  } `}
@@ -384,6 +403,8 @@ function ServiceItemMobile({
384
403
  renderIcon={renderIcon}
385
404
  serviceItem={serviceItem}
386
405
  isConexion={isConexion}
406
+ isAllinBus={isAllinBus}
407
+ boardingName={boardingName}
387
408
  />
388
409
  </div>
389
410
 
@@ -162,6 +162,7 @@ export interface MobileServiceItemProps {
162
162
  [key: string]: string | Record<string, string | undefined> | undefined;
163
163
  };
164
164
  useLottieFor?: string[];
165
+ stage_details_arr?: any;
165
166
  };
166
167
  onBookButtonPress?: () => void;
167
168
  onRemateUiButtonClick?: () => void;
@@ -249,4 +250,5 @@ export interface MobileServiceItemProps {
249
250
  wowDealData?: any;
250
251
  isFlores?: boolean;
251
252
  operatorLabel?: string;
253
+ isAllinBus?: any;
252
254
  }
@@ -2,6 +2,16 @@ import React from "react";
2
2
  import LottiePlayer from "../../assets/LottiePlayer";
3
3
  import commonService from "../../utils/CommonService";
4
4
 
5
+ if (typeof document !== "undefined") {
6
+ if (!document.getElementById("feature-spin-style")) {
7
+ const style = document.createElement("style");
8
+ style.id = "feature-spin-style";
9
+ style.textContent =
10
+ "@keyframes feature-spin { to { transform: rotate(360deg); } }";
11
+ document.head.appendChild(style);
12
+ }
13
+ }
14
+
5
15
  const HARDCODED_OPERATORS = [
6
16
  {
7
17
  logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Turbus_logo.svg/320px-Turbus_logo.svg.png",
@@ -523,7 +533,7 @@ const FeatureServiceUi = ({
523
533
  <span
524
534
  className="bold-text"
525
535
  ref={(node) =>
526
- commonService.startComprandoCount(node, 4, 16)
536
+ commonService.startComprandoDealCount(node, 4, 16)
527
537
  }
528
538
  style={{ fontVariantNumeric: "tabular-nums" }}
529
539
  />{" "}
@@ -628,20 +638,59 @@ const FeatureServiceUi = ({
628
638
 
629
639
  <button
630
640
  type="button"
631
- onClick={() => onBookButtonPress?.(ticketQuantity, serviceItem)}
632
- 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 text-center"
641
+ ref={(btn) => {
642
+ if (!btn) return;
643
+ btn.onclick = () => {
644
+ if (btn.disabled) return;
645
+ btn.disabled = true;
646
+ btn.style.cursor = "not-allowed";
647
+ btn.style.opacity = "0.85";
648
+ const normal = btn.querySelector(
649
+ '[data-btn-state="normal"]',
650
+ ) as HTMLElement | null;
651
+ const loading = btn.querySelector(
652
+ '[data-btn-state="loading"]',
653
+ ) as HTMLElement | null;
654
+ if (normal) normal.style.display = "none";
655
+ if (loading) loading.style.display = "flex";
656
+ onBookButtonPress?.(ticketQuantity, serviceItem);
657
+ };
658
+ }}
659
+ 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 text-center"
633
660
  style={{
634
661
  backgroundColor: "#FF5C60",
635
662
  whiteSpace: "nowrap",
663
+ minWidth: "120px",
636
664
  }}
637
665
  >
638
- <LottiePlayer
639
- // animationData={serviceItem.icons.flexibleAnim}
640
- animationData={getAnimationIcon("thunderAnimation")}
641
- width="16px"
642
- height="16px"
643
- />
644
- <span className="whitespace-nowrap">¡Lo quiero!</span>
666
+ <span
667
+ data-btn-state="normal"
668
+ style={{ display: "flex", alignItems: "center", gap: "6px" }}
669
+ >
670
+ <LottiePlayer
671
+ animationData={getAnimationIcon("thunderAnimation")}
672
+ width="16px"
673
+ height="16px"
674
+ />
675
+ <span className="whitespace-nowrap">¡Lo quiero!</span>
676
+ </span>
677
+ <span
678
+ data-btn-state="loading"
679
+ style={{ display: "none", alignItems: "center", gap: "6px" }}
680
+ >
681
+ <span
682
+ style={{
683
+ display: "inline-block",
684
+ width: "16px",
685
+ height: "16px",
686
+ border: "2px solid rgba(255,255,255,0.4)",
687
+ borderTopColor: "#fff",
688
+ borderRadius: "50%",
689
+ animation: "feature-spin 0.7s linear infinite",
690
+ flexShrink: 0,
691
+ }}
692
+ />
693
+ </span>
645
694
  </button>
646
695
  </div>
647
696
 
@@ -358,7 +358,8 @@ function SeatSection({
358
358
  : null;
359
359
 
360
360
  const operatorServiceName =
361
- serviceItem?.operator_service_name === "MovilBus" || "Movil Bus";
361
+ serviceItem?.operator_service_name === "MovilBus" ||
362
+ serviceItem?.operator_service_name === "Movil Bus";
362
363
 
363
364
  return (
364
365
  <>
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import LottiePlayer from "../../assets/LottiePlayer";
2
3
 
3
4
  interface ServiceBadgesProps {
4
5
  showTopLabel?: string;
@@ -13,6 +14,9 @@ interface ServiceBadgesProps {
13
14
  is_direct_trip?: boolean;
14
15
  train_type_label?: string;
15
16
  };
17
+ isAllinBus?: boolean;
18
+ boardingName?: string;
19
+ getAnimationIcon?: (iconKey: string) => any;
16
20
  }
17
21
 
18
22
  const ServiceBadges: React.FC<ServiceBadgesProps> = ({
@@ -22,6 +26,9 @@ const ServiceBadges: React.FC<ServiceBadgesProps> = ({
22
26
  renderIcon,
23
27
  translation,
24
28
  serviceItem,
29
+ isAllinBus = false,
30
+ boardingName,
31
+ getAnimationIcon,
25
32
  }) => {
26
33
  return (
27
34
  <div className="absolute -top-[10px] left-0 w-full flex items-center justify-end gap-[12px] pr-[22px] z-10">
@@ -89,6 +96,29 @@ const ServiceBadges: React.FC<ServiceBadgesProps> = ({
89
96
  <div>{"Tren Express"}</div>
90
97
  </div>
91
98
  )}
99
+ {boardingName && isAllinBus && (
100
+ <div
101
+ className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`}
102
+ style={{
103
+ backgroundColor: isSoldOut ? "#ddd" : colors.ratingBottomColor,
104
+ }}
105
+ >
106
+ <div className={isSoldOut ? "grayscale" : ""}>
107
+ <LottiePlayer
108
+ animationData={getAnimationIcon("priorityStageAnim")}
109
+ width="14px"
110
+ height="14px"
111
+ />
112
+ </div>
113
+ <div
114
+ className={
115
+ isSoldOut ? "text-white" : `text-[${colors.topLabelColor}]`
116
+ }
117
+ >
118
+ {boardingName}
119
+ </div>
120
+ </div>
121
+ )}
92
122
  </div>
93
123
  );
94
124
  };
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import LottiePlayer from "../../assets/LottiePlayer";
2
3
 
3
4
  interface ServiceBadgesMobileProps {
4
5
  showTopLabel?: string;
@@ -13,6 +14,8 @@ interface ServiceBadgesMobileProps {
13
14
  train_type_label?: string;
14
15
  };
15
16
  isConexion?: boolean;
17
+ isAllinBus?: any;
18
+ boardingName?: string;
16
19
  }
17
20
 
18
21
  const ServiceBadgesMobile: React.FC<ServiceBadgesMobileProps> = ({
@@ -22,6 +25,8 @@ const ServiceBadgesMobile: React.FC<ServiceBadgesMobileProps> = ({
22
25
  renderIcon,
23
26
  serviceItem,
24
27
  isConexion,
28
+ isAllinBus = false,
29
+ boardingName,
25
30
  }) => {
26
31
  return (
27
32
  <div className="absolute -top-[11px] left-0 w-full flex items-center justify-end gap-[12px] pr-[17px] z-10">
@@ -96,6 +101,50 @@ const ServiceBadgesMobile: React.FC<ServiceBadgesMobileProps> = ({
96
101
  <div>{"Tren Express"}</div>
97
102
  </div>
98
103
  )}
104
+ {boardingName && isAllinBus && (
105
+ <div
106
+ className={`flex items-center gap-[2p x] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] h-[24px] z-20`}
107
+ style={{
108
+ backgroundColor: "#fff",
109
+ border: isSoldOut
110
+ ? "1px solid #ccc"
111
+ : `1px solid ${colors.topLabelColor}`,
112
+ color: isSoldOut ? "#ccc" : colors.topLabelColor,
113
+ }}
114
+ >
115
+ <div
116
+ className={isSoldOut ? "grayscale" : ""}
117
+ style={{ marginTop: "1px" }}
118
+ >
119
+ {renderIcon("specialDeparture", "12px", { marginTop: "1px" })}
120
+ </div>
121
+ <div
122
+ style={{
123
+ color: isSoldOut ? "#ccc" : colors.topLabelColor,
124
+ }}
125
+ >
126
+ {boardingName}
127
+ </div>
128
+ </div>
129
+
130
+ // <div
131
+ // className={`flex items-center gap-[10px] py-[4px] px-[14px] rounded-[38px] text-[12.5px] z-20`}
132
+ // style={{
133
+ // backgroundColor: isSoldOut ? "#ddd" : colors.ratingBottomColor,
134
+ // }}
135
+ // >
136
+ // <div className={isSoldOut ? "grayscale" : ""}>
137
+ // {renderIcon("specialDeparture", "12px", { marginTop: "1px" })}
138
+ // </div>
139
+ // <div
140
+ // className={
141
+ // isSoldOut ? "text-white" : `text-[${colors.topLabelColor}]`
142
+ // }
143
+ // >
144
+ // {boardingName}
145
+ // </div>
146
+ // </div>
147
+ )}
99
148
  </div>
100
149
  );
101
150
  };
@@ -482,6 +482,54 @@ const commonService = {
482
482
  node.dataset.comprandoConfig = configKey;
483
483
  },
484
484
 
485
+ startComprandoDealCount: (
486
+ node: HTMLSpanElement | null,
487
+ min: number = 4,
488
+ max: number = 16,
489
+ ) => {
490
+ if (!node) return;
491
+
492
+ const configKey = `${min}-${max}`;
493
+ if (
494
+ node.dataset.comprandoDealId &&
495
+ node.dataset.comprandoDealConfig === configKey
496
+ ) {
497
+ return;
498
+ }
499
+
500
+ const prevId = node.dataset.comprandoDealId;
501
+ if (prevId) clearInterval(Number(prevId));
502
+
503
+ const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
504
+ node.textContent = String(initialValue);
505
+
506
+ const id = setInterval(() => {
507
+ const current = Number(node.textContent) || initialValue;
508
+ const changePercent = 0.05; // 5% change
509
+ const baseChange = Math.ceil(current * changePercent);
510
+
511
+ const rand = Math.random();
512
+ let change = baseChange;
513
+ if (baseChange <= 1) {
514
+ change = rand < 0.7 ? 1 : rand < 0.9 ? 2 : 3;
515
+ } else {
516
+ const variation = rand < 0.4 ? 0 : rand < 0.7 ? 1 : rand < 0.9 ? 2 : 3;
517
+ change = baseChange + variation;
518
+ }
519
+
520
+ // Only increase, never decrease
521
+ let next = current + change;
522
+
523
+ // Clamp within max
524
+ next = Math.min(max, next);
525
+
526
+ node.textContent = String(next);
527
+ }, 5000); // Update every 5 seconds
528
+
529
+ node.dataset.comprandoDealId = String(id);
530
+ node.dataset.comprandoDealConfig = configKey;
531
+ },
532
+
485
533
  timeToMinutes: (time: string): number => {
486
534
  const [h, m] = time.split(":").map(Number);
487
535
  return h * 60 + (m || 0);