@voyant-travel/catalog-react 0.117.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 (220) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +36 -0
  3. package/dist/admin/catalog-vertical-host.d.ts +45 -0
  4. package/dist/admin/catalog-vertical-host.d.ts.map +1 -0
  5. package/dist/admin/catalog-vertical-host.js +230 -0
  6. package/dist/admin/cruise-detail-host.d.ts +11 -0
  7. package/dist/admin/cruise-detail-host.d.ts.map +1 -0
  8. package/dist/admin/cruise-detail-host.js +33 -0
  9. package/dist/admin/dynamic-catalog-host.d.ts +13 -0
  10. package/dist/admin/dynamic-catalog-host.d.ts.map +1 -0
  11. package/dist/admin/dynamic-catalog-host.js +17 -0
  12. package/dist/admin/index.d.ts +133 -0
  13. package/dist/admin/index.d.ts.map +1 -0
  14. package/dist/admin/index.js +144 -0
  15. package/dist/admin/open-in-new-tab.d.ts +7 -0
  16. package/dist/admin/open-in-new-tab.d.ts.map +1 -0
  17. package/dist/admin/open-in-new-tab.js +10 -0
  18. package/dist/admin/pages/catalog-accommodations-detail-page.d.ts +4 -0
  19. package/dist/admin/pages/catalog-accommodations-detail-page.d.ts.map +1 -0
  20. package/dist/admin/pages/catalog-accommodations-detail-page.js +7 -0
  21. package/dist/admin/pages/catalog-accommodations-index-page.d.ts +8 -0
  22. package/dist/admin/pages/catalog-accommodations-index-page.d.ts.map +1 -0
  23. package/dist/admin/pages/catalog-accommodations-index-page.js +17 -0
  24. package/dist/admin/pages/catalog-cruises-detail-page.d.ts +4 -0
  25. package/dist/admin/pages/catalog-cruises-detail-page.d.ts.map +1 -0
  26. package/dist/admin/pages/catalog-cruises-detail-page.js +7 -0
  27. package/dist/admin/pages/catalog-cruises-index-page.d.ts +8 -0
  28. package/dist/admin/pages/catalog-cruises-index-page.d.ts.map +1 -0
  29. package/dist/admin/pages/catalog-cruises-index-page.js +19 -0
  30. package/dist/admin/pages/catalog-excursions-detail-page.d.ts +4 -0
  31. package/dist/admin/pages/catalog-excursions-detail-page.d.ts.map +1 -0
  32. package/dist/admin/pages/catalog-excursions-detail-page.js +7 -0
  33. package/dist/admin/pages/catalog-excursions-index-page.d.ts +8 -0
  34. package/dist/admin/pages/catalog-excursions-index-page.d.ts.map +1 -0
  35. package/dist/admin/pages/catalog-excursions-index-page.js +12 -0
  36. package/dist/admin/pages/catalog-products-detail-page.d.ts +8 -0
  37. package/dist/admin/pages/catalog-products-detail-page.d.ts.map +1 -0
  38. package/dist/admin/pages/catalog-products-detail-page.js +12 -0
  39. package/dist/admin/pages/catalog-products-index-page.d.ts +8 -0
  40. package/dist/admin/pages/catalog-products-index-page.d.ts.map +1 -0
  41. package/dist/admin/pages/catalog-products-index-page.js +12 -0
  42. package/dist/admin/pages/catalog-tours-detail-page.d.ts +4 -0
  43. package/dist/admin/pages/catalog-tours-detail-page.d.ts.map +1 -0
  44. package/dist/admin/pages/catalog-tours-detail-page.js +7 -0
  45. package/dist/admin/pages/catalog-tours-index-page.d.ts +8 -0
  46. package/dist/admin/pages/catalog-tours-index-page.d.ts.map +1 -0
  47. package/dist/admin/pages/catalog-tours-index-page.js +12 -0
  48. package/dist/admin/product-detail-host.d.ts +18 -0
  49. package/dist/admin/product-detail-host.d.ts.map +1 -0
  50. package/dist/admin/product-detail-host.js +40 -0
  51. package/dist/admin/scheduled-catalog-host.d.ts +15 -0
  52. package/dist/admin/scheduled-catalog-host.d.ts.map +1 -0
  53. package/dist/admin/scheduled-catalog-host.js +19 -0
  54. package/dist/admin/vertical-detail-host.d.ts +13 -0
  55. package/dist/admin/vertical-detail-host.d.ts.map +1 -0
  56. package/dist/admin/vertical-detail-host.js +62 -0
  57. package/dist/booking-engine/index.d.ts +26 -0
  58. package/dist/booking-engine/index.d.ts.map +1 -0
  59. package/dist/booking-engine/index.js +25 -0
  60. package/dist/booking-engine/use-booking-commit.d.ts +61 -0
  61. package/dist/booking-engine/use-booking-commit.d.ts.map +1 -0
  62. package/dist/booking-engine/use-booking-commit.js +47 -0
  63. package/dist/booking-engine/use-booking-draft-shape.d.ts +20 -0
  64. package/dist/booking-engine/use-booking-draft-shape.d.ts.map +1 -0
  65. package/dist/booking-engine/use-booking-draft-shape.js +9 -0
  66. package/dist/booking-engine/use-booking-draft.d.ts +102 -0
  67. package/dist/booking-engine/use-booking-draft.d.ts.map +1 -0
  68. package/dist/booking-engine/use-booking-draft.js +93 -0
  69. package/dist/booking-engine/use-booking-hold.d.ts +30 -0
  70. package/dist/booking-engine/use-booking-hold.d.ts.map +1 -0
  71. package/dist/booking-engine/use-booking-hold.js +44 -0
  72. package/dist/booking-engine/use-booking-journey-api.d.ts +23 -0
  73. package/dist/booking-engine/use-booking-journey-api.d.ts.map +1 -0
  74. package/dist/booking-engine/use-booking-journey-api.js +24 -0
  75. package/dist/booking-engine/use-booking-quote.d.ts +711 -0
  76. package/dist/booking-engine/use-booking-quote.d.ts.map +1 -0
  77. package/dist/booking-engine/use-booking-quote.js +122 -0
  78. package/dist/catalog-enrichment-mappers.d.ts +162 -0
  79. package/dist/catalog-enrichment-mappers.d.ts.map +1 -0
  80. package/dist/catalog-enrichment-mappers.js +190 -0
  81. package/dist/catalog-enrichment.d.ts +203 -0
  82. package/dist/catalog-enrichment.d.ts.map +1 -0
  83. package/dist/catalog-enrichment.js +130 -0
  84. package/dist/catalog-offers-client.d.ts +58 -0
  85. package/dist/catalog-offers-client.d.ts.map +1 -0
  86. package/dist/catalog-offers-client.js +61 -0
  87. package/dist/catalog-search-params.d.ts +45 -0
  88. package/dist/catalog-search-params.d.ts.map +1 -0
  89. package/dist/catalog-search-params.js +30 -0
  90. package/dist/catalog-surfaces.d.ts +17 -0
  91. package/dist/catalog-surfaces.d.ts.map +1 -0
  92. package/dist/catalog-surfaces.js +26 -0
  93. package/dist/client.d.ts +20 -0
  94. package/dist/client.d.ts.map +1 -0
  95. package/dist/client.js +65 -0
  96. package/dist/components/availability-calendar.d.ts +33 -0
  97. package/dist/components/availability-calendar.d.ts.map +1 -0
  98. package/dist/components/availability-calendar.js +65 -0
  99. package/dist/components/catalog-browse-page.d.ts +41 -0
  100. package/dist/components/catalog-browse-page.d.ts.map +1 -0
  101. package/dist/components/catalog-browse-page.js +47 -0
  102. package/dist/components/catalog-card.d.ts +68 -0
  103. package/dist/components/catalog-card.d.ts.map +1 -0
  104. package/dist/components/catalog-card.js +52 -0
  105. package/dist/components/catalog-detail-cruise-cards.d.ts +16 -0
  106. package/dist/components/catalog-detail-cruise-cards.d.ts.map +1 -0
  107. package/dist/components/catalog-detail-cruise-cards.js +54 -0
  108. package/dist/components/catalog-detail-departures.d.ts +25 -0
  109. package/dist/components/catalog-detail-departures.d.ts.map +1 -0
  110. package/dist/components/catalog-detail-departures.js +240 -0
  111. package/dist/components/catalog-detail-parts.d.ts +70 -0
  112. package/dist/components/catalog-detail-parts.d.ts.map +1 -0
  113. package/dist/components/catalog-detail-parts.js +282 -0
  114. package/dist/components/catalog-detail-sheet.d.ts +93 -0
  115. package/dist/components/catalog-detail-sheet.d.ts.map +1 -0
  116. package/dist/components/catalog-detail-sheet.js +68 -0
  117. package/dist/components/catalog-detail-view.d.ts +39 -0
  118. package/dist/components/catalog-detail-view.d.ts.map +1 -0
  119. package/dist/components/catalog-detail-view.js +157 -0
  120. package/dist/components/catalog-enrichment-fetchers.d.ts +8 -0
  121. package/dist/components/catalog-enrichment-fetchers.d.ts.map +1 -0
  122. package/dist/components/catalog-enrichment-fetchers.js +7 -0
  123. package/dist/components/catalog-faceted-filter.d.ts +30 -0
  124. package/dist/components/catalog-faceted-filter.d.ts.map +1 -0
  125. package/dist/components/catalog-faceted-filter.js +24 -0
  126. package/dist/components/catalog-filter-rail.d.ts +25 -0
  127. package/dist/components/catalog-filter-rail.d.ts.map +1 -0
  128. package/dist/components/catalog-filter-rail.js +88 -0
  129. package/dist/components/catalog-gallery.d.ts +27 -0
  130. package/dist/components/catalog-gallery.d.ts.map +1 -0
  131. package/dist/components/catalog-gallery.js +66 -0
  132. package/dist/components/catalog-hit.d.ts +27 -0
  133. package/dist/components/catalog-hit.d.ts.map +1 -0
  134. package/dist/components/catalog-hit.js +57 -0
  135. package/dist/components/catalog-page-cards.d.ts +21 -0
  136. package/dist/components/catalog-page-cards.d.ts.map +1 -0
  137. package/dist/components/catalog-page-cards.js +174 -0
  138. package/dist/components/catalog-page-config.d.ts +17 -0
  139. package/dist/components/catalog-page-config.d.ts.map +1 -0
  140. package/dist/components/catalog-page-config.js +369 -0
  141. package/dist/components/catalog-page.d.ts +88 -0
  142. package/dist/components/catalog-page.d.ts.map +1 -0
  143. package/dist/components/catalog-page.js +148 -0
  144. package/dist/components/catalog-range-filter.d.ts +34 -0
  145. package/dist/components/catalog-range-filter.d.ts.map +1 -0
  146. package/dist/components/catalog-range-filter.js +72 -0
  147. package/dist/components/catalog-search-page.d.ts +239 -0
  148. package/dist/components/catalog-search-page.d.ts.map +1 -0
  149. package/dist/components/catalog-search-page.js +63 -0
  150. package/dist/components/catalog-search-tab-panel.d.ts +42 -0
  151. package/dist/components/catalog-search-tab-panel.d.ts.map +1 -0
  152. package/dist/components/catalog-search-tab-panel.js +199 -0
  153. package/dist/components/catalog-vertical-detail-page.d.ts +33 -0
  154. package/dist/components/catalog-vertical-detail-page.d.ts.map +1 -0
  155. package/dist/components/catalog-vertical-detail-page.js +100 -0
  156. package/dist/components/cruise-detail-page-parts.d.ts +72 -0
  157. package/dist/components/cruise-detail-page-parts.d.ts.map +1 -0
  158. package/dist/components/cruise-detail-page-parts.js +146 -0
  159. package/dist/components/cruise-detail-page.d.ts +21 -0
  160. package/dist/components/cruise-detail-page.d.ts.map +1 -0
  161. package/dist/components/cruise-detail-page.js +201 -0
  162. package/dist/components/dynamic-catalog-page-parts.d.ts +40 -0
  163. package/dist/components/dynamic-catalog-page-parts.d.ts.map +1 -0
  164. package/dist/components/dynamic-catalog-page-parts.js +43 -0
  165. package/dist/components/dynamic-catalog-page.d.ts +23 -0
  166. package/dist/components/dynamic-catalog-page.d.ts.map +1 -0
  167. package/dist/components/dynamic-catalog-page.js +270 -0
  168. package/dist/components/media-gallery.d.ts +13 -0
  169. package/dist/components/media-gallery.d.ts.map +1 -0
  170. package/dist/components/media-gallery.js +42 -0
  171. package/dist/components/product-detail-page-parts.d.ts +106 -0
  172. package/dist/components/product-detail-page-parts.d.ts.map +1 -0
  173. package/dist/components/product-detail-page-parts.js +130 -0
  174. package/dist/components/product-detail-page.d.ts +57 -0
  175. package/dist/components/product-detail-page.d.ts.map +1 -0
  176. package/dist/components/product-detail-page.js +175 -0
  177. package/dist/components/scheduled-catalog-page.d.ts +34 -0
  178. package/dist/components/scheduled-catalog-page.d.ts.map +1 -0
  179. package/dist/components/scheduled-catalog-page.js +6 -0
  180. package/dist/hooks/index.d.ts +3 -0
  181. package/dist/hooks/index.d.ts.map +1 -0
  182. package/dist/hooks/index.js +2 -0
  183. package/dist/hooks/use-catalog-offers.d.ts +186 -0
  184. package/dist/hooks/use-catalog-offers.d.ts.map +1 -0
  185. package/dist/hooks/use-catalog-offers.js +105 -0
  186. package/dist/hooks/use-catalog-search.d.ts +109 -0
  187. package/dist/hooks/use-catalog-search.d.ts.map +1 -0
  188. package/dist/hooks/use-catalog-search.js +52 -0
  189. package/dist/i18n/en.d.ts +397 -0
  190. package/dist/i18n/en.d.ts.map +1 -0
  191. package/dist/i18n/en.js +396 -0
  192. package/dist/i18n/index.d.ts +5 -0
  193. package/dist/i18n/index.d.ts.map +1 -0
  194. package/dist/i18n/index.js +3 -0
  195. package/dist/i18n/messages.d.ts +335 -0
  196. package/dist/i18n/messages.d.ts.map +1 -0
  197. package/dist/i18n/messages.js +1 -0
  198. package/dist/i18n/provider.d.ts +816 -0
  199. package/dist/i18n/provider.d.ts.map +1 -0
  200. package/dist/i18n/provider.js +44 -0
  201. package/dist/i18n/ro.d.ts +397 -0
  202. package/dist/i18n/ro.d.ts.map +1 -0
  203. package/dist/i18n/ro.js +396 -0
  204. package/dist/index.d.ts +9 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +17 -0
  207. package/dist/provider.d.ts +2 -0
  208. package/dist/provider.d.ts.map +1 -0
  209. package/dist/provider.js +1 -0
  210. package/dist/schemas-catalog-offers.d.ts +290 -0
  211. package/dist/schemas-catalog-offers.d.ts.map +1 -0
  212. package/dist/schemas-catalog-offers.js +155 -0
  213. package/dist/schemas.d.ts +143 -0
  214. package/dist/schemas.d.ts.map +1 -0
  215. package/dist/schemas.js +76 -0
  216. package/dist/ui.d.ts +19 -0
  217. package/dist/ui.d.ts.map +1 -0
  218. package/dist/ui.js +18 -0
  219. package/package.json +150 -0
  220. package/src/styles.css +11 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-gallery.d.ts","sourceRoot":"","sources":["../../src/components/catalog-gallery.tsx"],"names":[],"mappings":"AAOA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAyBD,wBAAgB,OAAO,CAAC,EACtB,MAAM,EACN,WAAW,EACX,MAAM,GACP,EAAE;IACD,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC,2CA+GA;AAID,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,KAAK,EACL,OAAO,EACP,OAAO,EACP,MAAM,GACP,EAAE;IACD,MAAM,EAAE,YAAY,EAAE,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,MAAM,EAAE,qBAAqB,CAAA;CAC9B,kDAuFA"}
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Dialog, DialogContent } from "@voyant-travel/ui/components/dialog";
4
+ import { cn } from "@voyant-travel/ui/lib/utils";
5
+ import { ChevronLeft, ChevronRight, Image as ImageIcon, Maximize2, X } from "lucide-react";
6
+ import { useEffect } from "react";
7
+ function GalleryImg({ src }) {
8
+ return (_jsx("img", { src: src, alt: "", loading: "lazy", className: "h-full w-full object-cover transition duration-300 group-hover:scale-[1.03]" }));
9
+ }
10
+ function PhotosBadge({ count, label }) {
11
+ if (count <= 1)
12
+ return null;
13
+ return (_jsxs("span", { className: "pointer-events-none absolute right-3 bottom-3 flex items-center gap-1 rounded-md bg-background/90 px-2 py-1 text-xs shadow-sm backdrop-blur", children: [_jsx(Maximize2, { className: "h-3.5 w-3.5" }), " ", count, " ", label] }));
14
+ }
15
+ // Booking.com-style gallery mosaic — gap-free for any image count; every tile
16
+ // opens the lightbox at its index. Tuned for the common case (many photos)
17
+ // with graceful layouts for 1–4.
18
+ export function Gallery({ images, photosLabel, onOpen, }) {
19
+ const first = images[0];
20
+ if (!first) {
21
+ return (_jsx("div", { className: "flex aspect-[21/9] w-full items-center justify-center rounded-xl bg-muted text-muted-foreground", children: _jsx(ImageIcon, { className: "h-10 w-10", "aria-hidden": "true" }) }));
22
+ }
23
+ const tile = "group relative overflow-hidden bg-muted transition hover:opacity-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
24
+ if (images.length === 1) {
25
+ return (_jsx("button", { type: "button", onClick: () => onOpen(0), className: cn(tile, "block aspect-[21/9] w-full rounded-xl"), children: _jsx(GalleryImg, { src: first.src }) }));
26
+ }
27
+ if (images.length === 2) {
28
+ return (_jsx("div", { className: "grid h-[300px] grid-cols-2 gap-2 sm:h-[380px]", children: images.map((img, i) => (_jsxs("button", { type: "button", onClick: () => onOpen(i), className: cn(tile, "rounded-xl"), children: [_jsx(GalleryImg, { src: img.src }), i === 0 && _jsx(PhotosBadge, { count: images.length, label: photosLabel })] }, img.src))) }));
29
+ }
30
+ if (images.length === 3) {
31
+ return (_jsxs("div", { className: "grid h-[340px] grid-cols-2 grid-rows-2 gap-2 sm:h-[420px]", children: [_jsxs("button", { type: "button", onClick: () => onOpen(0), className: cn(tile, "row-span-2 rounded-xl"), children: [_jsx(GalleryImg, { src: first.src }), _jsx(PhotosBadge, { count: images.length, label: photosLabel })] }), images.slice(1, 3).map((img, i) => (_jsx("button", { type: "button", onClick: () => onOpen(i + 1), className: cn(tile, "rounded-lg"), children: _jsx(GalleryImg, { src: img.src }) }, img.src)))] }));
32
+ }
33
+ if (images.length === 4) {
34
+ return (_jsx("div", { className: "grid h-[340px] grid-cols-2 grid-rows-2 gap-2 sm:h-[420px]", children: images.map((img, i) => (_jsxs("button", { type: "button", onClick: () => onOpen(i), className: cn(tile, "rounded-lg"), children: [_jsx(GalleryImg, { src: img.src }), i === 0 && _jsx(PhotosBadge, { count: images.length, label: photosLabel })] }, img.src))) }));
35
+ }
36
+ // 5+: hero (2×2) + four tiles; the last tile shows "+N" when there are more.
37
+ const side = images.slice(1, 5);
38
+ const more = images.length - 5;
39
+ return (_jsxs("div", { className: "grid h-[340px] grid-cols-4 grid-rows-2 gap-2 sm:h-[440px]", children: [_jsxs("button", { type: "button", onClick: () => onOpen(0), className: cn(tile, "col-span-2 row-span-2 rounded-xl"), children: [_jsx(GalleryImg, { src: first.src }), _jsx(PhotosBadge, { count: images.length, label: photosLabel })] }), side.map((img, i) => (_jsxs("button", { type: "button", onClick: () => onOpen(i + 1), className: cn(tile, "rounded-lg"), children: [_jsx(GalleryImg, { src: img.src }), i === side.length - 1 && more > 0 && (_jsxs("span", { className: "absolute inset-0 flex items-center justify-center bg-black/50 font-medium text-sm text-white", children: ["+", more, " ", photosLabel] }))] }, img.src)))] }));
40
+ }
41
+ // Full-screen image lightbox (base-ui Dialog → Esc, focus trap, scroll lock).
42
+ // Arrow keys + on-screen arrows + a thumbnail rail navigate the gallery.
43
+ export function GalleryLightbox({ images, index, onIndex, onClose, labels, }) {
44
+ const count = images.length;
45
+ const go = (dir) => onIndex((index + dir + count) % count);
46
+ useEffect(() => {
47
+ const onKey = (e) => {
48
+ if (e.key === "ArrowRight")
49
+ onIndex((index + 1) % count);
50
+ else if (e.key === "ArrowLeft")
51
+ onIndex((index - 1 + count) % count);
52
+ };
53
+ window.addEventListener("keydown", onKey);
54
+ return () => window.removeEventListener("keydown", onKey);
55
+ }, [index, count, onIndex]);
56
+ const current = images[index] ?? images[0];
57
+ if (!current)
58
+ return null;
59
+ return (_jsx(Dialog, { open: true, onOpenChange: (o) => {
60
+ if (!o)
61
+ onClose();
62
+ }, children: _jsxs(DialogContent, { showCloseButton: false,
63
+ // `!` forces the width past the base dialog's `sm:max-w-md`; height is
64
+ // capped below the viewport so it reads as a panel, not full-screen.
65
+ className: "flex h-[88vh] max-h-[88vh] w-[95vw]! max-w-[1500px]! flex-col gap-0 rounded-xl border-0 bg-black/95 p-0 ring-0", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-3 text-white/90", children: [_jsxs("span", { className: "text-sm tabular-nums", children: [index + 1, " / ", count] }), _jsx("button", { type: "button", onClick: onClose, "aria-label": labels.close, className: "rounded-md p-1.5 hover:bg-white/10", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsxs("div", { className: "relative flex min-h-0 flex-1 items-center justify-center px-12 pb-2", children: [count > 1 && (_jsx("button", { type: "button", onClick: () => go(-1), "aria-label": labels.prev, className: "absolute left-3 rounded-full bg-white/10 p-2 text-white transition hover:bg-white/20", children: _jsx(ChevronLeft, { className: "h-6 w-6" }) })), _jsx("img", { src: current.src, alt: current.caption ?? "", className: "max-h-full max-w-full object-contain" }), count > 1 && (_jsx("button", { type: "button", onClick: () => go(1), "aria-label": labels.next, className: "absolute right-3 rounded-full bg-white/10 p-2 text-white transition hover:bg-white/20", children: _jsx(ChevronRight, { className: "h-6 w-6" }) }))] }), count > 1 && (_jsx("div", { className: "flex gap-2 overflow-x-auto px-4 py-3", children: images.map((img, i) => (_jsx("button", { type: "button", onClick: () => onIndex(i), className: cn("relative h-12 w-16 shrink-0 overflow-hidden rounded transition", i === index ? "ring-2 ring-white" : "opacity-50 hover:opacity-100"), children: _jsx("img", { src: img.src, alt: "", loading: "lazy", className: "h-full w-full object-cover" }) }, img.src))) }))] }) }));
66
+ }
@@ -0,0 +1,27 @@
1
+ import type { CatalogSearchHit } from "../index.js";
2
+ export type PriceUnit = "minor" | "major";
3
+ /**
4
+ * Field-access helpers shared between the catalog table columns
5
+ * (`catalog-page`) and the merchandising card (`catalog-card`). A search hit
6
+ * carries its indexer document under `document.fields`; values are weakly
7
+ * typed (`unknown`) because the index is vertical-agnostic, so every read
8
+ * coerces defensively.
9
+ */
10
+ /** Coerce a raw field value to a non-empty string, else `null`. */
11
+ export declare function asString(value: unknown): string | null;
12
+ /** Coerce a raw field value to a finite number, else `null`. */
13
+ export declare function asNumber(value: unknown): number | null;
14
+ /** Coerce a raw field value to a string array (dropping empties). */
15
+ export declare function asStringArray(value: unknown): string[];
16
+ /** Read a string field off a hit, falling back to the supplied value. */
17
+ export declare function stringField<T>(hit: CatalogSearchHit, key: string, fallback: T): string | T;
18
+ /** Read a numeric field off a hit, else `null`. */
19
+ export declare function numberField(hit: CatalogSearchHit, key: string): number | null;
20
+ /**
21
+ * Format a money field pair (integer cents + ISO currency) as a localized
22
+ * currency string with no fractional digits. Returns `null` when either side
23
+ * is missing.
24
+ */
25
+ export declare function formatHitPrice(hit: CatalogSearchHit, amountField: string, currencyField: string, unit?: PriceUnit): string | null;
26
+ export declare function resolveHitPriceUnit(hit: CatalogSearchHit, fallback?: PriceUnit, unitField?: string): PriceUnit;
27
+ //# sourceMappingURL=catalog-hit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-hit.d.ts","sourceRoot":"","sources":["../../src/components/catalog-hit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;AAEzC;;;;;;GAMG;AAEH,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEtD;AAED,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAOtD;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAGtD;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,MAAM,GAAG,CAAC,CAG1F;AAED,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,gBAAgB,EACrB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,IAAI,GAAE,SAAmB,GACxB,MAAM,GAAG,IAAI,CAUf;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,GAAE,SAAmB,EAC7B,SAAS,CAAC,EAAE,MAAM,GACjB,SAAS,CAGX"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Field-access helpers shared between the catalog table columns
3
+ * (`catalog-page`) and the merchandising card (`catalog-card`). A search hit
4
+ * carries its indexer document under `document.fields`; values are weakly
5
+ * typed (`unknown`) because the index is vertical-agnostic, so every read
6
+ * coerces defensively.
7
+ */
8
+ /** Coerce a raw field value to a non-empty string, else `null`. */
9
+ export function asString(value) {
10
+ return typeof value === "string" && value.length > 0 ? value : null;
11
+ }
12
+ /** Coerce a raw field value to a finite number, else `null`. */
13
+ export function asNumber(value) {
14
+ if (typeof value === "number" && Number.isFinite(value))
15
+ return value;
16
+ if (typeof value === "string") {
17
+ const n = Number(value);
18
+ return Number.isFinite(n) ? n : null;
19
+ }
20
+ return null;
21
+ }
22
+ /** Coerce a raw field value to a string array (dropping empties). */
23
+ export function asStringArray(value) {
24
+ if (!Array.isArray(value))
25
+ return [];
26
+ return value.filter((v) => typeof v === "string" && v.length > 0);
27
+ }
28
+ /** Read a string field off a hit, falling back to the supplied value. */
29
+ export function stringField(hit, key, fallback) {
30
+ const v = hit.document.fields[key];
31
+ return typeof v === "string" && v.length > 0 ? v : fallback;
32
+ }
33
+ /** Read a numeric field off a hit, else `null`. */
34
+ export function numberField(hit, key) {
35
+ return asNumber(hit.document.fields[key]);
36
+ }
37
+ /**
38
+ * Format a money field pair (integer cents + ISO currency) as a localized
39
+ * currency string with no fractional digits. Returns `null` when either side
40
+ * is missing.
41
+ */
42
+ export function formatHitPrice(hit, amountField, currencyField, unit = "minor") {
43
+ const amount = numberField(hit, amountField);
44
+ const currency = stringField(hit, currencyField, null);
45
+ if (amount == null || !currency)
46
+ return null;
47
+ const major = unit === "major" ? amount : amount / 100;
48
+ return new Intl.NumberFormat(undefined, {
49
+ style: "currency",
50
+ currency,
51
+ maximumFractionDigits: 0,
52
+ }).format(major);
53
+ }
54
+ export function resolveHitPriceUnit(hit, fallback = "minor", unitField) {
55
+ const value = unitField ? hit.document.fields[unitField] : null;
56
+ return value === "minor" || value === "major" ? value : fallback;
57
+ }
@@ -0,0 +1,21 @@
1
+ import type { CatalogCardConfig } from "./catalog-card.js";
2
+ import type { CatalogPageMessages } from "./catalog-page-config.js";
3
+ export declare function makeProductCard(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): CatalogCardConfig;
4
+ /** Resolve a board code (AI/HB/BB/RO/FB) to a localized, readable label. */
5
+ export declare function formatBoard(value: string | null, messages: CatalogPageMessages): string | null;
6
+ /** Resolve a transport code ("flight") to a readable label. */
7
+ export declare function formatTransport(value: string | null, messages: CatalogPageMessages): string | null;
8
+ /** Format a (possibly fractional) star rating as e.g. "4.5★". */
9
+ export declare function formatStars(value: unknown): string | null;
10
+ export declare function makeCruiseCard(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): CatalogCardConfig;
11
+ export declare function makeCharterCard(formatSupplier: (id: string | number) => string): CatalogCardConfig;
12
+ export declare function makeAccommodationCard(formatSupplier: (id: string | number) => string): CatalogCardConfig;
13
+ /** Resolve an ISO 3166 alpha-2 country code to a localized name (e.g. TR → Turkey). */
14
+ export declare function formatCountry(value: string | number): string;
15
+ /**
16
+ * Render a `YYYY-MM` departure-month facet value as a localized "Mon YYYY"
17
+ * label (e.g. `2027-03` → "Mar 2027"). Falls back to the raw value when it
18
+ * isn't a parseable month key.
19
+ */
20
+ export declare function formatDepartureMonth(value: unknown): string;
21
+ //# sourceMappingURL=catalog-page-cards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-page-cards.d.ts","sourceRoot":"","sources":["../../src/components/catalog-page-cards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAMnE,wBAAgB,eAAe,CAC7B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,iBAAiB,CAoBnB;AAUD,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAI9F;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,QAAQ,EAAE,mBAAmB,GAC5B,MAAM,GAAG,IAAI,CAGf;AAED,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIzD;AAED,wBAAgB,cAAc,CAC5B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,iBAAiB,CAuBnB;AAED,wBAAgB,eAAe,CAC7B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,GAC9C,iBAAiB,CAUnB;AAED,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,GAC9C,iBAAiB,CAMnB;AAeD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAQ5D;AA+DD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAO3D"}
@@ -0,0 +1,174 @@
1
+ import { asNumber, asString, asStringArray } from "./catalog-hit.js";
2
+ // Card configs ─────────────────────────────────────────────────────────────
3
+ // Each vertical's merchandising card is a declarative projection of indexed
4
+ // fields (no extra fetch). The grid view renders `CatalogCard` from these.
5
+ export function makeProductCard(formatSupplier, messages) {
6
+ return {
7
+ imageField: "thumbnailUrl",
8
+ // Prefer the computed lowest price; fall back to the headline sell price.
9
+ priceAmountField: ["priceFromAmountCents", "priceFromAmountMinor", "sellAmountCents"],
10
+ priceCurrencyField: ["priceFromCurrency", "sellCurrency"],
11
+ subtitle: productSubtitle,
12
+ meta: (fields) => durationMeta(fields, messages),
13
+ footerNote: (fields) => departureNote(fields, messages),
14
+ // Transport + board basis lead the chips, then categories/themes.
15
+ chips: (fields) => [
16
+ formatTransport(asString(fields.transport), messages),
17
+ formatBoard(asString(fields.board), messages),
18
+ ...asStringArray(fields.categories),
19
+ ]
20
+ .filter((v) => Boolean(v))
21
+ .slice(0, 3),
22
+ badges: (fields) => supplierBadge(fields, "supplierId", formatSupplier),
23
+ };
24
+ }
25
+ /** Product card subtitle: star rating + location (e.g. "4.5★ · Belek · Turkey"). */
26
+ function productSubtitle(fields) {
27
+ const parts = [formatStars(fields.stars), locationSubtitle(fields)].filter((v) => Boolean(v));
28
+ return parts.length > 0 ? parts.join(" · ") : null;
29
+ }
30
+ /** Resolve a board code (AI/HB/BB/RO/FB) to a localized, readable label. */
31
+ export function formatBoard(value, messages) {
32
+ if (!value)
33
+ return null;
34
+ const code = value.toUpperCase();
35
+ return messages.boards[code] ?? value;
36
+ }
37
+ /** Resolve a transport code ("flight") to a readable label. */
38
+ export function formatTransport(value, messages) {
39
+ if (!value)
40
+ return null;
41
+ return value === "flight" ? messages.card.flightIncluded : value;
42
+ }
43
+ /** Format a (possibly fractional) star rating as e.g. "4.5★". */
44
+ export function formatStars(value) {
45
+ const n = asNumber(value);
46
+ if (n == null || n <= 0)
47
+ return null;
48
+ return `${Number.isInteger(n) ? n : n.toFixed(1)}★`;
49
+ }
50
+ export function makeCruiseCard(formatSupplier, messages) {
51
+ return {
52
+ // Newly indexed cruise docs declare `lowestPriceUnit: "minor"`; legacy
53
+ // docs without that field stored `lowestPriceCached` as major units.
54
+ imageField: "thumbnailUrl",
55
+ priceAmountField: "lowestPriceCached",
56
+ priceCurrencyField: "lowestPriceCurrencyCached",
57
+ priceUnit: "major",
58
+ priceUnitField: "lowestPriceUnit",
59
+ subtitle: locationSubtitle,
60
+ meta: (fields) => nightsMeta(fields, messages),
61
+ // Next departure + how many sailings — sourced from the per-cruise sailing
62
+ // rollup (`earliestDepartureCached` / `departureCount`).
63
+ footerNote: (fields) => departureNote(fields, messages, {
64
+ dateField: "earliestDepartureCached",
65
+ countField: "departureCount",
66
+ withYear: true,
67
+ }),
68
+ chips: (fields) => [...asStringArray(fields.themes), ...asStringArray(fields.regions)].slice(0, 3),
69
+ badges: (fields) => supplierBadge(fields, "lineSupplierId", formatSupplier),
70
+ };
71
+ }
72
+ export function makeCharterCard(formatSupplier) {
73
+ return {
74
+ imageField: "heroImageUrl",
75
+ priceAmountField: "lowestPriceCachedAmount",
76
+ priceCurrencyField: "lowestPriceCachedCurrency",
77
+ subtitle: locationSubtitle,
78
+ chips: (fields) => [...asStringArray(fields.themes), ...asStringArray(fields.regions)].slice(0, 3),
79
+ badges: (fields) => supplierBadge(fields, "lineSupplierId", formatSupplier),
80
+ };
81
+ }
82
+ export function makeAccommodationCard(formatSupplier) {
83
+ return {
84
+ imageField: "thumbnailUrl",
85
+ subtitle: (fields) => asString(fields.roomClass),
86
+ badges: (fields) => supplierBadge(fields, "supplierId", formatSupplier),
87
+ };
88
+ }
89
+ function locationSubtitle(fields) {
90
+ const cities = asStringArray(fields.cities);
91
+ const regions = asStringArray(fields.regions);
92
+ const countries = asStringArray(fields.countries);
93
+ // Owned products carry resolved destination labels (cities/regions/countries);
94
+ // sourced rows carry raw `destinations` + ISO `countryCodes` from the upstream
95
+ // search document, so fall back to those and resolve the code to a name.
96
+ const place = cities[0] ?? regions[0] ?? asStringArray(fields.destinations)[0] ?? null;
97
+ const country = countries[0] ?? asStringArray(fields.countryCodes).map(formatCountry)[0] ?? null;
98
+ const parts = [...new Set([place, country].filter((v) => Boolean(v)))];
99
+ return parts.length > 0 ? parts.join(" · ") : null;
100
+ }
101
+ /** Resolve an ISO 3166 alpha-2 country code to a localized name (e.g. TR → Turkey). */
102
+ export function formatCountry(value) {
103
+ const code = String(value);
104
+ if (!/^[A-Za-z]{2}$/.test(code))
105
+ return code;
106
+ try {
107
+ return new Intl.DisplayNames(undefined, { type: "region" }).of(code.toUpperCase()) ?? code;
108
+ }
109
+ catch {
110
+ return code;
111
+ }
112
+ }
113
+ function durationMeta(fields, messages) {
114
+ const days = asNumber(fields.durationDays);
115
+ if (days == null || days < 1)
116
+ return null;
117
+ const nights = Math.max(0, days - 1);
118
+ return messages.card.daysNights
119
+ .replace("{days}", String(days))
120
+ .replace("{nights}", String(nights));
121
+ }
122
+ function nightsMeta(fields, messages) {
123
+ const nights = asNumber(fields.nights);
124
+ if (nights == null || nights < 1)
125
+ return null;
126
+ return messages.card.nights.replace("{nights}", String(nights));
127
+ }
128
+ function departureNote(fields, messages, opts = {}) {
129
+ const next = asString(fields[opts.dateField ?? "nextDepartureDate"]);
130
+ const count = asNumber(fields[opts.countField ?? "availableDeparturesCount"]);
131
+ const parts = [];
132
+ if (next)
133
+ parts.push(messages.card.nextDeparture.replace("{date}", formatShortDate(next, opts.withYear)));
134
+ if (count != null && count > 0) {
135
+ parts.push(count === 1
136
+ ? messages.card.oneDeparture
137
+ : messages.card.departures.replace("{count}", String(count)));
138
+ }
139
+ return parts.length > 0 ? parts.join(" · ") : null;
140
+ }
141
+ function supplierBadge(fields, supplierField, formatSupplier) {
142
+ const id = asString(fields[supplierField]);
143
+ if (!id)
144
+ return [];
145
+ // The supplier (e.g. "TUI") is the merchandising signal operators care
146
+ // about — more than the sourcing channel (Voyant Connect), which stays a
147
+ // filter facet + a detail-sheet attribute.
148
+ return [{ label: formatSupplier(id), variant: "secondary" }];
149
+ }
150
+ function formatShortDate(iso, withYear = false) {
151
+ const date = new Date(iso);
152
+ if (Number.isNaN(date.getTime()))
153
+ return iso;
154
+ return new Intl.DateTimeFormat(undefined, {
155
+ day: "numeric",
156
+ month: "short",
157
+ ...(withYear ? { year: "numeric" } : {}),
158
+ }).format(date);
159
+ }
160
+ /**
161
+ * Render a `YYYY-MM` departure-month facet value as a localized "Mon YYYY"
162
+ * label (e.g. `2027-03` → "Mar 2027"). Falls back to the raw value when it
163
+ * isn't a parseable month key.
164
+ */
165
+ export function formatDepartureMonth(value) {
166
+ const raw = String(value);
167
+ const match = /^(\d{4})-(\d{2})$/.exec(raw);
168
+ if (!match)
169
+ return raw;
170
+ const date = new Date(Number(match[1]), Number(match[2]) - 1, 1);
171
+ if (Number.isNaN(date.getTime()))
172
+ return raw;
173
+ return new Intl.DateTimeFormat(undefined, { month: "short", year: "numeric" }).format(date);
174
+ }
@@ -0,0 +1,17 @@
1
+ import type { ColumnDef } from "@tanstack/react-table";
2
+ import type { useCatalogUiMessagesOrDefault } from "../i18n/index.js";
3
+ import type { CatalogSearchHit } from "../index.js";
4
+ import type { CatalogFilterField } from "./catalog-search-page.js";
5
+ export type CatalogPageMessages = ReturnType<typeof useCatalogUiMessagesOrDefault>["catalogPage"];
6
+ export declare function makeProductColumns(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): ColumnDef<CatalogSearchHit, unknown>[];
7
+ export declare function makeExtraColumns(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): ColumnDef<CatalogSearchHit, unknown>[];
8
+ export declare function makeCruiseColumns(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): ColumnDef<CatalogSearchHit, unknown>[];
9
+ export declare function makeCharterColumns(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): ColumnDef<CatalogSearchHit, unknown>[];
10
+ export declare function makeAccommodationColumns(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): ColumnDef<CatalogSearchHit, unknown>[];
11
+ export declare function makeProductFilters(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): CatalogFilterField[];
12
+ export declare function makeExtraFilters(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): CatalogFilterField[];
13
+ export declare function makeCruiseFilters(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): CatalogFilterField[];
14
+ export declare function makeCharterFilters(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): CatalogFilterField[];
15
+ export declare function makeAccommodationFilters(formatSupplier: (id: string | number) => string, messages: CatalogPageMessages): CatalogFilterField[];
16
+ export declare function formatSourceKind(value: unknown, messages: CatalogPageMessages): string;
17
+ //# sourceMappingURL=catalog-page-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-page-config.d.ts","sourceRoot":"","sources":["../../src/components/catalog-page-config.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAItD,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAenD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAElE,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,6BAA6B,CAAC,CAAC,aAAa,CAAC,CAAA;AAEjG,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,EAAE,CAUxC;AAED,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,EAAE,CAUxC;AAED,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,EAAE,CAiBxC;AAED,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,EAAE,CAcxC;AAED,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,EAAE,CAUxC;AAED,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,kBAAkB,EAAE,CAmDtB;AAED,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,kBAAkB,EAAE,CA2BtB;AAED,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,kBAAkB,EAAE,CA+BtB;AAED,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,kBAAkB,EAAE,CAgCtB;AAED,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,MAAM,EAC/C,QAAQ,EAAE,mBAAmB,GAC5B,kBAAkB,EAAE,CAmCtB;AA8GD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,GAAG,MAAM,CAqBtF"}