kupos-ui-components-lib 9.3.1 → 9.3.3

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 (41) hide show
  1. package/dist/assets/images/anims/service_list/60_percent_anim.json +1 -0
  2. package/dist/assets/images/anims/service_list/dot_animation.json +1 -0
  3. package/dist/assets/images/anims/service_list/thunder_icon.json +1 -0
  4. package/dist/components/ServiceItem/ServiceItemDesktop.js +50 -121
  5. package/dist/components/ServiceItem/ServiceItemMobile.js +44 -105
  6. package/dist/components/ServiceItem/mobileTypes.d.ts +3 -0
  7. package/dist/components/ServiceItem/types.d.ts +2 -0
  8. package/dist/styles.css +125 -23
  9. package/dist/types.d.ts +2 -0
  10. package/dist/ui/ExpendedDropDown/ExpandedDropdown.js +16 -19
  11. package/dist/ui/FeatureServiceUI/FeatureServiceUi.d.ts +12 -0
  12. package/dist/ui/FeatureServiceUI/FeatureServiceUi.js +101 -0
  13. package/dist/ui/SeatSection/SeatSection.d.ts +2 -1
  14. package/dist/ui/SeatSection/SeatSection.js +41 -10
  15. package/dist/ui/ServiceBadges/ServiceBadges.d.ts +17 -0
  16. package/dist/ui/ServiceBadges/ServiceBadges.js +33 -0
  17. package/dist/ui/mobileweb/DateTimeSectionMobile.d.ts +2 -1
  18. package/dist/ui/mobileweb/DateTimeSectionMobile.js +2 -2
  19. package/dist/ui/mobileweb/ExpandedDropdownMobile.js +18 -18
  20. package/dist/ui/mobileweb/SeatSectionMobile.d.ts +2 -1
  21. package/dist/ui/mobileweb/SeatSectionMobile.js +28 -16
  22. package/dist/ui/mobileweb/ServiceBadgesMobile.d.ts +17 -0
  23. package/dist/ui/mobileweb/ServiceBadgesMobile.js +35 -0
  24. package/dist/utils/CommonService.d.ts +7 -0
  25. package/dist/utils/CommonService.js +61 -0
  26. package/package.json +1 -1
  27. package/src/assets/images/anims/service_list/dot_animation.json +1 -0
  28. package/src/components/ServiceItem/ServiceItemDesktop.tsx +93 -235
  29. package/src/components/ServiceItem/ServiceItemMobile.tsx +118 -166
  30. package/src/components/ServiceItem/mobileTypes.ts +3 -0
  31. package/src/components/ServiceItem/types.ts +2 -0
  32. package/src/styles.css +10 -0
  33. package/src/types.ts +2 -0
  34. package/src/ui/ExpendedDropDown/ExpandedDropdown.tsx +26 -67
  35. package/src/ui/SeatSection/SeatSection.tsx +87 -32
  36. package/src/ui/ServiceBadges/ServiceBadges.tsx +92 -0
  37. package/src/ui/mobileweb/DateTimeSectionMobile.tsx +3 -0
  38. package/src/ui/mobileweb/ExpandedDropdownMobile.tsx +24 -24
  39. package/src/ui/mobileweb/SeatSectionMobile.tsx +77 -32
  40. package/src/ui/mobileweb/ServiceBadgesMobile.tsx +92 -0
  41. package/src/utils/CommonService.ts +86 -0
@@ -31,7 +31,7 @@ const TimeRow = ({ label, icon, alt, date, timeContent, isSoldOut, }) => (React.
31
31
  React.createElement("span", { className: "cursor-pointer black-text" }, DateService.getServiceItemDate(date)),
32
32
  React.createElement("div", { className: "absolute left-[50%]" }, "\u2022"),
33
33
  React.createElement("div", { className: "font-[900] relative black-text" }, timeContent)))));
34
- function DateTimeSectionMobile({ onBookButtonPress, isCiva, isSoldOut, isLinatal, isPeru, orignLabel, destinationLabel, originIcon, destinationIcon, travelDate, arrivalDate, depTime, arrTime, seatTypes, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, }) {
34
+ function DateTimeSectionMobile({ onBookButtonPress, isCiva, isSoldOut, isLinatal, isPeru, orignLabel, destinationLabel, originIcon, destinationIcon, travelDate, arrivalDate, depTime, arrTime, seatTypes, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, tooltipBgColor, }) {
35
35
  const { cleaned: cleanedDepTime, hasAM, hasPM } = getCleanedDepTime(depTime);
36
36
  const depTimeContent = isLinatal ? (React.createElement("div", null,
37
37
  React.createElement("span", null,
@@ -52,6 +52,6 @@ function DateTimeSectionMobile({ onBookButtonPress, isCiva, isSoldOut, isLinatal
52
52
  backgroundColor: "#ccc",
53
53
  margin: "auto",
54
54
  } })),
55
- React.createElement(SeatSectionMobile, { seatTypes: seatTypes, isSoldOut: isSoldOut, isPeru: isPeru, seatPriceColor: seatPriceColor, currencySign: currencySign, availableSeats: availableSeats, removeDuplicateSeats: removeDuplicateSeats, serviceItem: serviceItem })));
55
+ React.createElement(SeatSectionMobile, { seatTypes: seatTypes, isSoldOut: isSoldOut, isPeru: isPeru, seatPriceColor: seatPriceColor, currencySign: currencySign, availableSeats: availableSeats, removeDuplicateSeats: removeDuplicateSeats, serviceItem: serviceItem, tooltipBgColor: tooltipBgColor })));
56
56
  }
57
57
  export default DateTimeSectionMobile;
@@ -7,6 +7,23 @@ function ExpandedDropdownMobile({ serviceItem, isPeru, petSeatInfo, petFriendlyA
7
7
  opacity: isSoldOut ? 0.5 : 1,
8
8
  } },
9
9
  React.createElement("div", { className: "flex flex-col gap-[8px] text-[11px] min-[420px]:text-[12px] text-[#464647]", style: { lineHeight: 1.6 } },
10
+ isPeru ? null : isChangeTicket ? (React.createElement("div", { className: "flex gap-[6px]" },
11
+ React.createElement("span", { style: { marginTop: "2px" } }, "\u2022"),
12
+ React.createElement("span", null,
13
+ React.createElement("span", { className: "bold-text" }, "Pasaje flexible:"),
14
+ " Tu pasaje puede ser cambiado de manera online",
15
+ " ",
16
+ React.createElement("span", { className: "bold-text" },
17
+ "hasta ",
18
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.change_ticket_hours) || 6,
19
+ " horas antes"),
20
+ " ",
21
+ "de la salida del bus. El monto ser\u00E1 reembolsado a tu billetera kupospay."))) : (React.createElement("div", { className: "flex gap-[8px] " },
22
+ React.createElement("span", { style: { marginTop: "2px" } }, "\u2022"),
23
+ React.createElement("span", null,
24
+ React.createElement("span", null,
25
+ React.createElement("span", { className: "bold-text" }, "Pasaje flexible:"),
26
+ " Esta empresa no permite cambios de pasajes")))),
10
27
  petSeatInfo && Object.keys(petSeatInfo).length > 0 ? (React.createElement("div", { className: "flex items-center" },
11
28
  React.createElement("div", { className: `relative group cursor-default ` },
12
29
  React.createElement("div", { className: "flex items-center" },
@@ -24,23 +41,6 @@ function ExpandedDropdownMobile({ serviceItem, isPeru, petSeatInfo, petFriendlyA
24
41
  React.createElement("span", { className: "bold-text" }, "Pol\u00EDticas de anulaci\u00F3n:"),
25
42
  " Tu pasaje puede ser anulado de forma online",
26
43
  React.createElement("span", { className: "bold-text" }, " hasta 4 horas antes"),
27
- " antes de la salida del bus.")),
28
- isPeru ? null : isChangeTicket ? (React.createElement("div", { className: "flex gap-[6px]" },
29
- React.createElement("span", { style: { marginTop: "2px" } }, "\u2022"),
30
- React.createElement("span", null,
31
- React.createElement("span", { className: "bold-text" }, "Pol\u00EDticas de cambios:"),
32
- " Tu pasaje puede ser cambiado de manera online",
33
- " ",
34
- React.createElement("span", { className: "bold-text" },
35
- "hasta ",
36
- (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.change_ticket_hours) || 6,
37
- " horas antes"),
38
- " ",
39
- "de la salida del bus. El monto ser\u00E1 reembolsado a tu billetera kupospay."))) : (React.createElement("div", { className: "flex gap-[8px] " },
40
- React.createElement("span", { style: { marginTop: "2px" } }, "\u2022"),
41
- React.createElement("span", null,
42
- React.createElement("span", null,
43
- React.createElement("span", { className: "bold-text" }, "Pol\u00EDtica de cambios:"),
44
- " Esta empresa no permite cambios de pasajes")))))));
44
+ " antes de la salida del bus.")))));
45
45
  }
46
46
  export default ExpandedDropdownMobile;
@@ -13,6 +13,7 @@ interface SeatSectionMobileProps {
13
13
  availableSeats: number;
14
14
  removeDuplicateSeats?: boolean;
15
15
  serviceItem?: any;
16
+ tooltipBgColor?: string;
16
17
  }
17
- declare function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, }: SeatSectionMobileProps): React.ReactElement;
18
+ declare function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, tooltipBgColor, }: SeatSectionMobileProps): React.ReactElement;
18
19
  export default SeatSectionMobile;
@@ -42,8 +42,8 @@ const getUniqueSeats = (data, limit) => {
42
42
  .sort((a, b) => a.fare - b.fare)
43
43
  .slice(0, limit);
44
44
  };
45
- function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, }) {
46
- var _a, _b, _c;
45
+ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPriceColor, currencySign, availableSeats, removeDuplicateSeats, serviceItem, tooltipBgColor, }) {
46
+ var _a, _b, _c, _d, _e;
47
47
  const hasMultipleTypes = ((_a = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.length) !== null && _a !== void 0 ? _a : 0) > 2;
48
48
  const getFare = (fare) => {
49
49
  if (removeDuplicateSeats && availableSeats <= 0 && !isPeru) {
@@ -59,10 +59,6 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
59
59
  return null;
60
60
  return filtered.reduce((min, item) => (item.fare < min ? item.fare : min), filtered[0].fare);
61
61
  };
62
- const getHighestFare = () => {
63
- var _a;
64
- return (((_a = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => !EXCEPTIONS.includes(item.label))) === null || _a === void 0 ? void 0 : _a.reduce((max, item) => (item.fare > max ? item.fare : max), 0)) || 0);
65
- };
66
62
  const renderPeruSeats = () => {
67
63
  const lowestFare = getLowestFare();
68
64
  if (lowestFare === null)
@@ -92,17 +88,33 @@ function SeatSectionMobile({ seatTypes: seatTypesData, isSoldOut, isPeru, seatPr
92
88
  ? getUniqueSeats(seatTypesData, 3)
93
89
  : (_c = (_b = seatTypesData === null || seatTypesData === void 0 ? void 0 : seatTypesData.filter((item) => getFilteredSeats(item.label))) === null || _b === void 0 ? void 0 : _b.sort((a, b) => a.fare - b.fare)) === null || _c === void 0 ? void 0 : _c.slice(0, 2);
94
90
  const discountedSeats = seats === null || seats === void 0 ? void 0 : seats.map((seat) => (Object.assign(Object.assign({}, seat), commonService.calculateDiscountedPrice(seat.fare, serviceItem))));
95
- const highestOriginalPrice = Math.max(...((discountedSeats === null || discountedSeats === void 0 ? void 0 : discountedSeats.map((s) => s.originalPrice)) || [0]));
96
91
  const hasDiscount = discountedSeats === null || discountedSeats === void 0 ? void 0 : discountedSeats.some((s) => s.originalPrice !== s.discountedPrice);
97
- return (React.createElement("div", { className: "content-center relative", style: { width: "40%" } },
98
- !isPeru && hasDiscount && (React.createElement("div", { className: "absolute -top-[16px] right-[0] flex justify-end" },
99
- React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] line-through", style: { color: "#bbb" } }, commonService.currency(highestOriginalPrice, currencySign)))),
100
- React.createElement("div", { className: "flex flex-col justify-between h-[2.5rem] ", style: {
101
- gap: isSoldOut ? "0px" : "5px",
102
- justifyContent: hasMultipleTypes ? "space-between" : "center",
92
+ const discountSeat = (_d = discountedSeats === null || discountedSeats === void 0 ? void 0 : discountedSeats.filter((seat) => !EXCEPTIONS.includes(seat.label))) === null || _d === void 0 ? void 0 : _d.sort((a, b) => a.discountedPrice - b.discountedPrice)[0];
93
+ const discountValue = (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discount_type) === "percentage" &&
94
+ typeof (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.discount_value) === "number"
95
+ ? Math.round(serviceItem.discount_value)
96
+ : null;
97
+ return (React.createElement("div", { className: "content-center relative", style: { width: "40%" } }, hasDiscount && discountSeat ? (React.createElement("div", { className: "relative grid grid-cols-[auto_auto] justify-between gap-x-[8px] " },
98
+ discountValue != null && (React.createElement("div", { className: "absolute -top-[18px] right-[0px]", style: {
99
+ animation: "pulse-zoom 2s ease-in-out infinite",
103
100
  } },
104
- renderSeats(),
105
- isSoldOut ? (React.createElement("div", { className: "flex justify-end" },
106
- React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] text-[#ccc]" }, "Agotado"))) : null)));
101
+ React.createElement("span", { className: "rounded-[100px] px-[8px] text-[12px] bold-text leading-[20px] text-[#fff]", style: {
102
+ backgroundColor: tooltipBgColor,
103
+ } },
104
+ discountValue,
105
+ "% OFF"))),
106
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] leading-[20px] text-[#c2c2c2]" }, "Antes"),
107
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] leading-[20px] text-[#9f9f9f] line-through text-right" }, commonService.currency(discountSeat.originalPrice, currencySign)),
108
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] leading-[24px]", style: { color: isSoldOut ? "#bbb" : "#464647" } }, "Desde"),
109
+ React.createElement("span", { className: "flex items-center justify-end gap-[4px] text-[14px] bold-text leading-[24px]", style: { color: isSoldOut ? "#bbb" : "#ff5964" } },
110
+ ((_e = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _e === void 0 ? void 0 : _e.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[16px] w-[16px] object-contain", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
111
+ commonService.currency(discountSeat.discountedPrice, currencySign)),
112
+ isSoldOut ? (React.createElement("span", { className: "col-span-2 min-[420]:text-[13px] text-right text-[12px] text-[#ccc]" }, "Agotado")) : null)) : (React.createElement("div", { className: "flex flex-col justify-between h-[2.5rem] ", style: {
113
+ gap: isSoldOut ? "0px" : "5px",
114
+ justifyContent: hasMultipleTypes ? "space-between" : "center",
115
+ } },
116
+ renderSeats(),
117
+ isSoldOut ? (React.createElement("div", { className: "flex justify-end" },
118
+ React.createElement("span", { className: "min-[420]:text-[13px] text-[12px] text-[#ccc]" }, "Agotado"))) : null))));
107
119
  }
108
120
  export default SeatSectionMobile;
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ interface ServiceBadgesMobileProps {
3
+ showTopLabel?: string;
4
+ isSoldOut: boolean;
5
+ colors: any;
6
+ renderIcon: (iconKey: string, size?: string) => React.ReactNode;
7
+ translation?: {
8
+ directService?: string;
9
+ };
10
+ serviceItem: {
11
+ is_direct_trip?: boolean;
12
+ train_type_label?: string;
13
+ };
14
+ isConexion?: boolean;
15
+ }
16
+ declare const ServiceBadgesMobile: React.FC<ServiceBadgesMobileProps>;
17
+ export default ServiceBadgesMobile;
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ const ServiceBadgesMobile = ({ showTopLabel, isSoldOut, colors, renderIcon, serviceItem, isConexion, }) => {
3
+ return (React.createElement("div", { className: "absolute -top-[9px] left-0 w-full flex items-center justify-end gap-[12px] pr-[20px] z-10" },
4
+ showTopLabel && (React.createElement("div", { className: `flex items-center gap-[2px] py-[4px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20`, style: {
5
+ backgroundColor: "#fff",
6
+ border: `1px solid ${colors.topLabelColor}`,
7
+ color: colors.topLabelColor,
8
+ } },
9
+ React.createElement("div", { className: isSoldOut ? "grayscale" : "" }, renderIcon("specialDeparture", "12px")),
10
+ React.createElement("div", { style: {
11
+ color: colors.topLabelColor,
12
+ } }, showTopLabel))),
13
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.is_direct_trip) && (React.createElement("div", { className: `flex items-center gap-[2px] py-[5px] px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20`, style: {
14
+ backgroundColor: "#fff",
15
+ border: `1px solid ${colors.topLabelColor}`,
16
+ color: colors.topLabelColor,
17
+ } },
18
+ renderIcon("directo", "12px"),
19
+ React.createElement("div", { className: "ml-[5px]" }, "Directo"))),
20
+ isConexion && (React.createElement("div", { className: `flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[11px] z-20`, style: {
21
+ backgroundColor: "#fff",
22
+ border: `1px solid ${colors.topLabelColor}`,
23
+ color: colors.topLabelColor,
24
+ } },
25
+ renderIcon("airportIcon", "14px"),
26
+ React.createElement("div", null, "Conexi\u00F3n"))),
27
+ (serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.train_type_label) === "Tren Express (Nuevo)" && (React.createElement("div", { className: `flex items-center gap-[2px] py-[5px] text-white px-[10px] rounded-[38px] min-[420]:text-[12px] text-[10px] z-20`, style: {
28
+ backgroundColor: "#fff",
29
+ border: `1px solid ${colors.topLabelColor}`,
30
+ color: colors.topLabelColor,
31
+ } },
32
+ renderIcon("directo", "12px"),
33
+ React.createElement("div", null, "Tren Express")))));
34
+ };
35
+ export default ServiceBadgesMobile;
@@ -17,5 +17,12 @@ declare const commonService: {
17
17
  originalPrice: number;
18
18
  discountedPrice: number;
19
19
  };
20
+ startViewerCount: (node: HTMLSpanElement | null, viewersConfig?: {
21
+ min: number;
22
+ max: number;
23
+ interval?: number;
24
+ }) => void;
25
+ startCountdown: (node: HTMLSpanElement | null, countdownSeconds?: number) => void;
26
+ startComprandoCount: (node: HTMLSpanElement | null, min?: number, max?: number) => void;
20
27
  };
21
28
  export default commonService;
@@ -284,5 +284,66 @@ const commonService = {
284
284
  const discountedPrice = Math.max(0, price - finalDiscount);
285
285
  return { originalPrice: price, discountedPrice };
286
286
  },
287
+ startViewerCount: (node, viewersConfig) => {
288
+ if (!node || !viewersConfig)
289
+ return;
290
+ const prevId = node.dataset.viewerId;
291
+ if (prevId)
292
+ clearInterval(Number(prevId));
293
+ const { min, max, interval = 5000 } = viewersConfig;
294
+ const clamp = (v) => Math.min(max, Math.max(min, v));
295
+ const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
296
+ node.textContent = String(initialValue);
297
+ const id = setInterval(() => {
298
+ const current = Number(node.textContent) || initialValue;
299
+ const delta = Math.ceil(current * 0.2);
300
+ const next = current + Math.floor(Math.random() * (2 * delta + 1)) - delta;
301
+ node.textContent = String(clamp(Math.round(next)));
302
+ }, interval);
303
+ node.dataset.viewerId = String(id);
304
+ },
305
+ startCountdown: (node, countdownSeconds = 599) => {
306
+ if (!node)
307
+ return;
308
+ const prevId = node.dataset.countdownId;
309
+ if (prevId)
310
+ clearInterval(Number(prevId));
311
+ let remaining = countdownSeconds * 1000; // Convert to milliseconds
312
+ const formatTime = (totalMs) => {
313
+ const m = Math.floor(totalMs / 60000);
314
+ const s = Math.floor((totalMs % 60000) / 1000);
315
+ const ms = Math.floor((totalMs % 1000) / 10); // Show 2 digits for milliseconds
316
+ return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}:${String(ms).padStart(2, "0")}`;
317
+ };
318
+ node.textContent = formatTime(remaining);
319
+ const id = setInterval(() => {
320
+ remaining -= 100; // Decrease by 100ms
321
+ if (remaining <= 0) {
322
+ remaining = countdownSeconds * 1000;
323
+ }
324
+ node.textContent = formatTime(remaining);
325
+ }, 100); // Update every 100ms
326
+ node.dataset.countdownId = String(id);
327
+ },
328
+ startComprandoCount: (node, min = 4, max = 16) => {
329
+ if (!node)
330
+ return;
331
+ const prevId = node.dataset.comprandoId;
332
+ if (prevId)
333
+ clearInterval(Number(prevId));
334
+ const initialValue = Math.floor(Math.random() * (max - min + 1)) + min;
335
+ node.textContent = String(initialValue);
336
+ const id = setInterval(() => {
337
+ const current = Number(node.textContent) || initialValue;
338
+ const changePercent = 0.05; // 5% change
339
+ const change = Math.ceil(current * changePercent);
340
+ const direction = Math.random() > 0.5 ? 1 : -1;
341
+ let next = current + (change * direction);
342
+ // Clamp within min and max
343
+ next = Math.min(max, Math.max(min, next));
344
+ node.textContent = String(next);
345
+ }, 5000); // Update every 5 seconds
346
+ node.dataset.comprandoId = String(id);
347
+ },
287
348
  };
288
349
  export default commonService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "9.3.1",
3
+ "version": "9.3.3",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1 @@
1
+ {"nm":"Main Scene","h":260.03,"w":256,"meta":{"g":"@lottiefiles/creator@1.89.0"},"layers":[{"ty":4,"nm":"circle 3","sr":1,"st":12,"op":91,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-96.841,-18.544,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[1000,1000],"t":2},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[600,600],"t":28},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[400,400],"t":40},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[600,600],"t":53},{"s":[1000,1000],"t":78}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[128,130.015]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100],"t":2},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[70],"t":28},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[20],"t":40},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[70],"t":53},{"s":[100],"t":78}]}},"shapes":[{"ty":"gr","nm":"Ellipse 1","it":[{"ty":"el","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0]},"s":{"a":0,"k":[23.352,23.352]}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-96.841,-18.544]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":1}],"v":"5.7.0","fr":29.97,"op":84,"ip":0,"assets":[]}