kupos-ui-components-lib 9.10.10 → 9.11.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.
package/dist/styles.css CHANGED
@@ -1274,6 +1274,13 @@
1274
1274
  }
1275
1275
  }
1276
1276
  }
1277
+ .hover\:z-\[200\] {
1278
+ &:hover {
1279
+ @media (hover: hover) {
1280
+ z-index: 200;
1281
+ }
1282
+ }
1283
+ }
1277
1284
  .hover\:z-\[500\] {
1278
1285
  &:hover {
1279
1286
  @media (hover: hover) {
@@ -86,7 +86,8 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
86
86
  ? selectedSlotService.original_price
87
87
  : originalPrice;
88
88
  const displaySavingsPercent = selectedSlotService && selectedSlotService.original_price
89
- ? Math.round(((selectedSlotService.original_price - selectedSlotService.final_price) /
89
+ ? Math.round(((selectedSlotService.original_price -
90
+ selectedSlotService.final_price) /
90
91
  selectedSlotService.original_price) *
91
92
  100)
92
93
  : savingsPercent;
@@ -173,7 +174,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
173
174
  animationData: getAnimationIcon("flameAnimation"), width: "18px", height: "18px" })),
174
175
  React.createElement("span", { className: "bold-text" }, "Remate"),
175
176
  "\u00A0t\u00E9rmina en \u00A0",
176
- React.createElement("span", { className: "bold-text text-end", ref: (node) => commonService.startCountdown(node, getCountdownSeconds()), style: {
177
+ React.createElement("span", { className: "bold-text text-end", ref: (node) => commonService.startDealCountdown(node, getCountdownSeconds()), style: {
177
178
  fontVariantNumeric: "tabular-nums",
178
179
  display: "inline-block",
179
180
  color: "#FF5C60",
@@ -210,7 +211,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
210
211
  filter: "brightness(0) invert(1)",
211
212
  } })),
212
213
  isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
213
- React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
214
+ React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)] hover:z-[200]", style: {
214
215
  zIndex: 20,
215
216
  backgroundColor: "#fff",
216
217
  borderRadius: "14px",
@@ -282,7 +283,7 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, isSoldOut, getAnimationIc
282
283
  ((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
283
284
  "/images/service-list/bus-icon.svg";
284
285
  }, className: `h-[24px] max-w-full object-contain ${isSoldOut ? "grayscale" : ""}` }),
285
- React.createElement("span", { className: "text-[11px] truncate max-w-full text-center text-[white]" }, op.name),
286
+ React.createElement("span", { className: "text-[11px] truncate max-w-full text-center text-[white] whitespace-nowrap " }, op.name),
286
287
  React.createElement("div", { className: "bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap" },
287
288
  React.createElement("span", null, op === null || op === void 0 ? void 0 : op.time)),
288
289
  React.createElement("span", { className: "text-[10px] mt-[6px] text-[white]" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))))),
@@ -58,6 +58,7 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
58
58
  var _a;
59
59
  const uniqueSeats = getUniqueSeats(seatTypes);
60
60
  const sortedSeatTypes = getSortedSeatTypes(seatTypes);
61
+ console.log("🚀 ~ SeatSection ~ sortedSeatTypes:", sortedSeatTypes);
61
62
  const numberOfSeats = getNumberOfSeats(seatTypes);
62
63
  const isCentered = numberOfSeats < 2 || removeDuplicateSeats;
63
64
  const formatPrice = (price) => availableSeats <= 0
@@ -73,6 +74,17 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
73
74
  };
74
75
  const renderSeatPrices = () => {
75
76
  if (isPeru) {
77
+ const isMovilBus = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
78
+ // Multiple unique seat types → show a price row for each (MovilBus only)
79
+ if (isMovilBus && uniqueSeats.length > 1) {
80
+ return uniqueSeats
81
+ .filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
82
+ .map((val, key) => {
83
+ const { discountedPrice } = CommonService.calculateDiscountedPrice(val.price, serviceItem);
84
+ return (React.createElement("span", { key: key, className: "flex items-center text-[13.33px] bold-text" }, formatPrice(discountedPrice)));
85
+ });
86
+ }
87
+ // Single seat type → original behaviour (one lowest-fare price)
76
88
  const allSeats = getAllSeatTypes(seatTypes);
77
89
  const lowestFare = allSeats.length > 0 ? allSeats[0].price : 0;
78
90
  const { discountedPrice } = CommonService.calculateDiscountedPrice(lowestFare, serviceItem);
@@ -116,13 +128,27 @@ function SeatSection({ seatTypes, availableSeats, isSoldOut, priceColor, currenc
116
128
  })();
117
129
  const renderLabels = () => {
118
130
  if (isPeru) {
131
+ const isMovilBus = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
132
+ // Multiple unique seat types → show a label row for each (MovilBus only)
133
+ if (isMovilBus && uniqueSeats.length > 1) {
134
+ return uniqueSeats
135
+ .filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
136
+ .map((val, key) => (React.createElement("span", { key: key, className: `flex items-center justify-between text-[13.33px] ${isSoldOut ? "text-[#c0c0c0]" : ""}` }, CommonService.truncateSeatLabel(val.label))));
137
+ }
138
+ // Single seat type → original behaviour
139
+ const seats = removeDuplicateSeats ? uniqueSeats : sortedSeatTypes;
140
+ const filteredSeats = seats.filter((s) => !SEAT_EXCEPTIONS.includes(s.label));
141
+ const seatLabel = filteredSeats.length > 0
142
+ ? removeDuplicateSeats
143
+ ? CommonService.truncateSeatLabel(filteredSeats[0].label)
144
+ : filteredSeats[0].label
145
+ : null;
146
+ const operatorServiceName = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
119
147
  return (React.createElement(React.Fragment, null,
120
148
  hasDiscount && (React.createElement("span", { className: "text-[13.33px]", style: {
121
149
  color: "#999",
122
- // position: "relative",
123
- // bottom: numberOfSeats ? "10px" : "",
124
150
  } }, "Antes")),
125
- React.createElement("span", { className: "text-[13.33px]" }, "Desde")));
151
+ React.createElement("span", { className: "text-[13.33px] flex flex-col" }, operatorServiceName ? (React.createElement("span", { className: "text-[13.33px]" }, seatLabel)) : (React.createElement("span", { className: "text-[13.33px]" }, "Desde")))));
126
152
  }
127
153
  return renderSeatNames();
128
154
  };
@@ -64,13 +64,29 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
64
64
  if (lowestFare === null)
65
65
  return null;
66
66
  const priceColor = isSoldOut ? "#bbb" : seatPriceColor;
67
+ const isMovilBus = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_service_name) === "MovilBus";
68
+ // Fetch ALL unique seats (no slice limit) for the multi-row MovilBus case
69
+ const uniqueSeats = getUniqueSeats(seatTypesData !== null && seatTypesData !== void 0 ? seatTypesData : [], Infinity);
70
+ // MovilBus + multiple unique seat types → render a row per seat
71
+ if (isMovilBus && uniqueSeats.length > 1) {
72
+ return (React.createElement(React.Fragment, null, uniqueSeats.map((seat, key) => {
73
+ const { discountedPrice } = commonService.calculateDiscountedPrice(Number(seat.fare), serviceItem);
74
+ return (React.createElement("div", { key: key, className: "w-[100%] flex flex-row justify-between items-center" },
75
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: isSoldOut ? "#bbb" : "#464647" } }, commonService.truncateSeatLabel(seat.label)),
76
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: priceColor } }, commonService.currency(discountedPrice, currencySign))));
77
+ })));
78
+ }
79
+ // Single seat type (or non-MovilBus) → original behaviour
67
80
  const { originalPrice, discountedPrice } = commonService.calculateDiscountedPrice(lowestFare, serviceItem);
81
+ const seatLabel = uniqueSeats.length > 0
82
+ ? commonService.truncateSeatLabel(uniqueSeats[0].label)
83
+ : null;
68
84
  return (React.createElement(React.Fragment, null,
69
85
  originalPrice !== discountedPrice && (React.createElement("div", { className: "w-[100%] flex flex-row justify-between items-center" },
70
86
  React.createElement("span", { className: "min-[420]:text-[13px] text-[12px]", style: { color: "#bbb" } }, "Antes"),
71
87
  React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] line-through", style: { color: "#bbb" } }, commonService.currency(originalPrice, currencySign)))),
72
88
  React.createElement("div", { className: "w-[100%] flex flex-row justify-between items-center" },
73
- React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: isSoldOut ? "#bbb" : "#464647" } }, "Desde"),
89
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: isSoldOut ? "#bbb" : "#464647" } }, isMovilBus ? seatLabel || "Desde" : "Desde"),
74
90
  React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] bold-text", style: { color: priceColor } }, commonService.currency(discountedPrice, currencySign)))));
75
91
  };
76
92
  const renderDpSeats = () => {
@@ -27,6 +27,7 @@ declare const commonService: {
27
27
  interval?: number;
28
28
  }) => void;
29
29
  startCountdown: (node: HTMLSpanElement | null, countdownSeconds?: number) => void;
30
+ startDealCountdown: (node: HTMLSpanElement | null, countdownSeconds: number) => void;
30
31
  startComprandoCount: (node: HTMLSpanElement | null, min?: number, max?: number) => void;
31
32
  timeToMinutes: (time: string) => number;
32
33
  minutesToTime: (minutes: number) => string;
@@ -348,6 +348,39 @@ const commonService = {
348
348
  }, 1000);
349
349
  node.dataset.countdownId = String(id);
350
350
  },
351
+ startDealCountdown: (node, countdownSeconds) => {
352
+ if (!node)
353
+ return;
354
+ if (node.dataset.dealTimerStarted)
355
+ return;
356
+ node.dataset.dealTimerStarted = "true";
357
+ const prevId = node.dataset.dealCountdownId;
358
+ if (prevId)
359
+ clearInterval(Number(prevId));
360
+ let remaining = Math.max(0, Math.floor(countdownSeconds));
361
+ const formatTime = (totalSeconds) => {
362
+ if (totalSeconds <= 0)
363
+ return "Expirado";
364
+ const h = Math.floor(totalSeconds / 3600);
365
+ const m = Math.floor((totalSeconds % 3600) / 60);
366
+ const s = totalSeconds % 60;
367
+ if (h > 0) {
368
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
369
+ }
370
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
371
+ };
372
+ node.textContent = formatTime(remaining);
373
+ if (remaining <= 0)
374
+ return;
375
+ const id = setInterval(() => {
376
+ remaining -= 1;
377
+ node.textContent = formatTime(remaining);
378
+ if (remaining <= 0) {
379
+ clearInterval(Number(node.dataset.dealCountdownId));
380
+ }
381
+ }, 1000);
382
+ node.dataset.dealCountdownId = String(id);
383
+ },
351
384
  startComprandoCount: (node, min = 4, max = 16) => {
352
385
  if (!node)
353
386
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.10.10",
3
+ "version": "9.11.1",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -120,13 +120,15 @@ const FeatureServiceUi = ({
120
120
  const displayOriginalPrice = selectedSlotService
121
121
  ? selectedSlotService.original_price
122
122
  : originalPrice;
123
- const displaySavingsPercent = selectedSlotService && selectedSlotService.original_price
124
- ? Math.round(
125
- ((selectedSlotService.original_price - selectedSlotService.final_price) /
126
- selectedSlotService.original_price) *
127
- 100,
128
- )
129
- : savingsPercent;
123
+ const displaySavingsPercent =
124
+ selectedSlotService && selectedSlotService.original_price
125
+ ? Math.round(
126
+ ((selectedSlotService.original_price -
127
+ selectedSlotService.final_price) /
128
+ selectedSlotService.original_price) *
129
+ 100,
130
+ )
131
+ : savingsPercent;
130
132
 
131
133
  // The label shown on the dropdown button
132
134
  const departureRange =
@@ -231,7 +233,7 @@ const FeatureServiceUi = ({
231
233
  <span
232
234
  className="bold-text text-end"
233
235
  ref={(node) =>
234
- commonService.startCountdown(node, getCountdownSeconds())
236
+ commonService.startDealCountdown(node, getCountdownSeconds())
235
237
  }
236
238
  style={{
237
239
  fontVariantNumeric: "tabular-nums",
@@ -326,7 +328,7 @@ const FeatureServiceUi = ({
326
328
  {isThisTimeDropdownOpen && (
327
329
  <>
328
330
  <div
329
- className="absolute left-0 top-[calc(100%+10px)]"
331
+ className="absolute left-0 top-[calc(100%+10px)] hover:z-[200]"
330
332
  style={{
331
333
  zIndex: 20,
332
334
  backgroundColor: "#fff",
@@ -472,7 +474,7 @@ const FeatureServiceUi = ({
472
474
  isSoldOut ? "grayscale" : ""
473
475
  }`}
474
476
  />
475
- <span className="text-[11px] truncate max-w-full text-center text-[white]">
477
+ <span className="text-[11px] truncate max-w-full text-center text-[white] whitespace-nowrap ">
476
478
  {op.name}
477
479
  </span>
478
480
  <div className="bg-[#FF8F45] text-white text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap">
@@ -107,6 +107,7 @@ function SeatSection({
107
107
  }: SeatSectionProps): React.ReactElement {
108
108
  const uniqueSeats = getUniqueSeats(seatTypes);
109
109
  const sortedSeatTypes = getSortedSeatTypes(seatTypes);
110
+ console.log("🚀 ~ SeatSection ~ sortedSeatTypes:", sortedSeatTypes);
110
111
  const numberOfSeats = getNumberOfSeats(seatTypes);
111
112
  const isCentered = numberOfSeats < 2 || removeDuplicateSeats;
112
113
 
@@ -138,6 +139,26 @@ function SeatSection({
138
139
 
139
140
  const renderSeatPrices = () => {
140
141
  if (isPeru) {
142
+ const isMovilBus = serviceItem?.operator_service_name === "MovilBus";
143
+
144
+ // Multiple unique seat types → show a price row for each (MovilBus only)
145
+ if (isMovilBus && uniqueSeats.length > 1) {
146
+ return uniqueSeats
147
+ .filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
148
+ .map((val, key) => {
149
+ const { discountedPrice } = CommonService.calculateDiscountedPrice(
150
+ val.price,
151
+ serviceItem,
152
+ );
153
+ return (
154
+ <span key={key} className="flex items-center text-[13.33px] bold-text">
155
+ {formatPrice(discountedPrice)}
156
+ </span>
157
+ );
158
+ });
159
+ }
160
+
161
+ // Single seat type → original behaviour (one lowest-fare price)
141
162
  const allSeats = getAllSeatTypes(seatTypes);
142
163
  const lowestFare = allSeats.length > 0 ? allSeats[0].price : 0;
143
164
  const { discountedPrice } = CommonService.calculateDiscountedPrice(
@@ -217,6 +238,39 @@ function SeatSection({
217
238
 
218
239
  const renderLabels = () => {
219
240
  if (isPeru) {
241
+ const isMovilBus = serviceItem?.operator_service_name === "MovilBus";
242
+
243
+ // Multiple unique seat types → show a label row for each (MovilBus only)
244
+ if (isMovilBus && uniqueSeats.length > 1) {
245
+ return uniqueSeats
246
+ .filter((s) => !SEAT_EXCEPTIONS.includes(s.label))
247
+ .map((val, key) => (
248
+ <span
249
+ key={key}
250
+ className={`flex items-center justify-between text-[13.33px] ${
251
+ isSoldOut ? "text-[#c0c0c0]" : ""
252
+ }`}
253
+ >
254
+ {CommonService.truncateSeatLabel(val.label)}
255
+ </span>
256
+ ));
257
+ }
258
+
259
+ // Single seat type → original behaviour
260
+ const seats = removeDuplicateSeats ? uniqueSeats : sortedSeatTypes;
261
+ const filteredSeats = seats.filter(
262
+ (s) => !SEAT_EXCEPTIONS.includes(s.label),
263
+ );
264
+ const seatLabel =
265
+ filteredSeats.length > 0
266
+ ? removeDuplicateSeats
267
+ ? CommonService.truncateSeatLabel(filteredSeats[0].label)
268
+ : filteredSeats[0].label
269
+ : null;
270
+
271
+ const operatorServiceName =
272
+ serviceItem?.operator_service_name === "MovilBus";
273
+
220
274
  return (
221
275
  <>
222
276
  {hasDiscount && (
@@ -224,14 +278,19 @@ function SeatSection({
224
278
  className="text-[13.33px]"
225
279
  style={{
226
280
  color: "#999",
227
- // position: "relative",
228
- // bottom: numberOfSeats ? "10px" : "",
229
281
  }}
230
282
  >
231
283
  Antes
232
284
  </span>
233
285
  )}
234
- <span className="text-[13.33px]">Desde</span>
286
+
287
+ <span className="text-[13.33px] flex flex-col">
288
+ {operatorServiceName ? (
289
+ <span className="text-[13.33px]">{seatLabel}</span>
290
+ ) : (
291
+ <span className="text-[13.33px]">Desde</span>
292
+ )}
293
+ </span>
235
294
  </>
236
295
  );
237
296
  }
@@ -318,7 +377,7 @@ function SeatSection({
318
377
  <div className="col-start-2 row-start-3 flex h-[30px] items-end justify-center relative">
319
378
  <span
320
379
  className="flex items-center gap-[6px] text-[22px] bold-text leading-[30px]"
321
- style={{ color: isSoldOut ? "#c0c0c0" : dpSeatColor||"#ff5964" }}
380
+ style={{ color: isSoldOut ? "#c0c0c0" : dpSeatColor || "#ff5964" }}
322
381
  >
323
382
  <div
324
383
  className="absolute"
@@ -149,9 +149,53 @@ function SeatSectionMobile({
149
149
  if (lowestFare === null) return null;
150
150
 
151
151
  const priceColor = isSoldOut ? "#bbb" : seatPriceColor;
152
+ const isMovilBus = serviceItem?.operator_service_name === "MovilBus";
153
+
154
+ // Fetch ALL unique seats (no slice limit) for the multi-row MovilBus case
155
+ const uniqueSeats = getUniqueSeats(seatTypesData ?? [], Infinity as number);
156
+
157
+ // MovilBus + multiple unique seat types → render a row per seat
158
+ if (isMovilBus && uniqueSeats.length > 1) {
159
+ return (
160
+ <>
161
+ {uniqueSeats.map((seat, key) => {
162
+ const { discountedPrice } = commonService.calculateDiscountedPrice(
163
+ Number(seat.fare),
164
+ serviceItem,
165
+ );
166
+ return (
167
+ <div
168
+ key={key}
169
+ className="w-[100%] flex flex-row justify-between items-center"
170
+ >
171
+ <span
172
+ className="min-[420]:text-[13px] text-[12px] bold-text"
173
+ style={{ color: isSoldOut ? "#bbb" : "#464647" }}
174
+ >
175
+ {commonService.truncateSeatLabel(seat.label)}
176
+ </span>
177
+ <span
178
+ className="min-[420]:text-[13px] text-[12px] bold-text"
179
+ style={{ color: priceColor }}
180
+ >
181
+ {commonService.currency(discountedPrice, currencySign)}
182
+ </span>
183
+ </div>
184
+ );
185
+ })}
186
+ </>
187
+ );
188
+ }
189
+
190
+ // Single seat type (or non-MovilBus) → original behaviour
152
191
  const { originalPrice, discountedPrice } =
153
192
  commonService.calculateDiscountedPrice(lowestFare, serviceItem);
154
193
 
194
+ const seatLabel =
195
+ uniqueSeats.length > 0
196
+ ? commonService.truncateSeatLabel(uniqueSeats[0].label)
197
+ : null;
198
+
155
199
  return (
156
200
  <>
157
201
  {originalPrice !== discountedPrice && (
@@ -175,7 +219,7 @@ function SeatSectionMobile({
175
219
  className="min-[420]:text-[13px] text-[12px] bold-text"
176
220
  style={{ color: isSoldOut ? "#bbb" : "#464647" }}
177
221
  >
178
- Desde
222
+ {isMovilBus ? seatLabel || "Desde" : "Desde"}
179
223
  </span>
180
224
  <span
181
225
  className="min-[420]:text-[13px] text-[12px] bold-text"
@@ -399,6 +399,45 @@ const commonService = {
399
399
  node.dataset.countdownId = String(id);
400
400
  },
401
401
 
402
+ startDealCountdown: (
403
+ node: HTMLSpanElement | null,
404
+ countdownSeconds: number,
405
+ ) => {
406
+ if (!node) return;
407
+ if (node.dataset.dealTimerStarted) return;
408
+ node.dataset.dealTimerStarted = "true";
409
+
410
+ const prevId = node.dataset.dealCountdownId;
411
+ if (prevId) clearInterval(Number(prevId));
412
+
413
+ let remaining = Math.max(0, Math.floor(countdownSeconds));
414
+
415
+ const formatTime = (totalSeconds: number) => {
416
+ if (totalSeconds <= 0) return "Expirado";
417
+ const h = Math.floor(totalSeconds / 3600);
418
+ const m = Math.floor((totalSeconds % 3600) / 60);
419
+ const s = totalSeconds % 60;
420
+ if (h > 0) {
421
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
422
+ }
423
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
424
+ };
425
+
426
+ node.textContent = formatTime(remaining);
427
+
428
+ if (remaining <= 0) return;
429
+
430
+ const id = setInterval(() => {
431
+ remaining -= 1;
432
+ node.textContent = formatTime(remaining);
433
+ if (remaining <= 0) {
434
+ clearInterval(Number(node.dataset.dealCountdownId));
435
+ }
436
+ }, 1000);
437
+
438
+ node.dataset.dealCountdownId = String(id);
439
+ },
440
+
402
441
  startComprandoCount: (
403
442
  node: HTMLSpanElement | null,
404
443
  min: number = 4,