@salla.sa/twilight-components 3.0.24 → 3.0.26

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 (388) hide show
  1. package/dist/cjs/{Helper-sq6ztayx.js → Helper-Di1G8EnP.js} +1 -1
  2. package/dist/cjs/{filepond-BMxlEH8M.js → filepond-DU_-t59i.js} +1 -1
  3. package/dist/cjs/{filepond-plugin-file-poster-DVSz4StJ.js → filepond-plugin-file-poster-CYpSqb6i.js} +1 -1
  4. package/dist/cjs/{filepond-plugin-file-validate-size-CXJiLSzt.js → filepond-plugin-file-validate-size-a_tUhUBx.js} +1 -1
  5. package/dist/cjs/{filepond-plugin-file-validate-type-DWK85msl.js → filepond-plugin-file-validate-type-D_X7irXu.js} +1 -1
  6. package/dist/cjs/{filepond-plugin-image-edit-BHttJI5X.js → filepond-plugin-image-edit-BRP3GZ31.js} +1 -1
  7. package/dist/cjs/{filepond-plugin-image-exif-orientation-DTKIO2zE.js → filepond-plugin-image-exif-orientation-CiHIpg-4.js} +1 -1
  8. package/dist/cjs/{filepond-plugin-image-preview-BaRxwhFa.js → filepond-plugin-image-preview-CKT6x8T1.js} +1 -1
  9. package/dist/cjs/{index-BJ3-fFSF.js → index-BLPUgwXq.js} +6 -6
  10. package/dist/cjs/info-Do5bVPmK.js +13 -0
  11. package/dist/cjs/{intlTelInput-DNoOb73M.js → intlTelInput-CyL5UDfU.js} +1 -1
  12. package/dist/cjs/loader.cjs.js +2 -2
  13. package/dist/cjs/salla-accordion-body_2.cjs.entry.js +1 -1
  14. package/dist/cjs/salla-accordion_6.cjs.entry.js +2 -2
  15. package/dist/cjs/salla-add-product-button_5.cjs.entry.js +2 -2
  16. package/dist/cjs/salla-advertisement.cjs.entry.js +1 -1
  17. package/dist/cjs/salla-alert_2.cjs.entry.js +3 -9
  18. package/dist/cjs/salla-app-install-alert.cjs.entry.js +1 -1
  19. package/dist/cjs/salla-applied-filters.cjs.entry.js +2 -2
  20. package/dist/cjs/salla-apps-icons.cjs.entry.js +1 -1
  21. package/dist/cjs/salla-attachment-view_9.cjs.entry.js +11 -11
  22. package/dist/cjs/salla-badge.cjs.entry.js +1 -1
  23. package/dist/cjs/salla-basket-gap.cjs.entry.js +1 -1
  24. package/dist/cjs/salla-bullet-delivery_3.cjs.entry.js +1 -1
  25. package/dist/cjs/salla-cart-coupons.cjs.entry.js +1 -1
  26. package/dist/cjs/salla-cart-item-offers_2.cjs.entry.js +5 -1
  27. package/dist/cjs/salla-cart-summary-card.cjs.entry.js +120 -5
  28. package/dist/cjs/salla-cashback-banner.cjs.entry.js +1 -1
  29. package/dist/cjs/salla-comment-factors.cjs.entry.js +1 -1
  30. package/dist/cjs/salla-comment-form_9.cjs.entry.js +2 -2
  31. package/dist/cjs/salla-conditional-offer.cjs.entry.js +628 -19
  32. package/dist/cjs/salla-contacts.cjs.entry.js +1 -1
  33. package/dist/cjs/salla-cookies-bar.cjs.entry.js +1 -1
  34. package/dist/cjs/salla-count-down.cjs.entry.js +1 -1
  35. package/dist/cjs/salla-custom-fields.cjs.entry.js +1 -1
  36. package/dist/cjs/salla-delivery-promise.cjs.entry.js +1 -1
  37. package/dist/cjs/salla-edit-order-button.cjs.entry.js +1 -1
  38. package/dist/cjs/salla-filters-widget.cjs.entry.js +2 -2
  39. package/dist/cjs/salla-filters.cjs.entry.js +2 -2
  40. package/dist/cjs/salla-free-shipping-banner.cjs.entry.js +1 -1
  41. package/dist/cjs/salla-free-shipping-tag.cjs.entry.js +1 -1
  42. package/dist/cjs/salla-fulfillment-methods.cjs.entry.js +1 -1
  43. package/dist/cjs/salla-gift-card-redeem_2.cjs.entry.js +1 -1
  44. package/dist/cjs/salla-gifting.cjs.entry.js +1 -1
  45. package/dist/cjs/salla-hook.cjs.entry.js +1 -1
  46. package/dist/cjs/salla-infinite-scroll.cjs.entry.js +2 -2
  47. package/dist/cjs/salla-installment.cjs.entry.js +1 -1
  48. package/dist/cjs/salla-list-tile.cjs.entry.js +1 -1
  49. package/dist/cjs/salla-localization-modal.cjs.entry.js +1 -1
  50. package/dist/cjs/salla-login-modal.cjs.entry.js +1 -1
  51. package/dist/cjs/salla-loyalty-banner_10.cjs.entry.js +1 -1
  52. package/dist/cjs/salla-loyalty-exchange-product-card.cjs.entry.js +1 -1
  53. package/dist/cjs/salla-loyalty-points-banner.cjs.entry.js +1 -1
  54. package/dist/cjs/salla-loyalty-points-history.cjs.entry.js +1 -1
  55. package/dist/cjs/salla-loyalty-prize-item.cjs.entry.js +1 -1
  56. package/dist/cjs/salla-loyalty.cjs.entry.js +1 -1
  57. package/dist/cjs/salla-maintenance-alert.cjs.entry.js +1 -1
  58. package/dist/cjs/salla-map_3.cjs.entry.js +1 -1
  59. package/dist/cjs/salla-menu.cjs.entry.js +1 -1
  60. package/dist/cjs/salla-metadata.cjs.entry.js +1 -1
  61. package/dist/cjs/salla-mobile-update-modal.cjs.entry.js +1 -1
  62. package/dist/cjs/salla-multiple-bundle-product-cart_2.cjs.entry.js +1 -1
  63. package/dist/cjs/salla-multiple-bundle-product-options-modal_2.cjs.entry.js +1 -1
  64. package/dist/cjs/salla-multiple-bundle-product.cjs.entry.js +1 -1
  65. package/dist/cjs/salla-next-order-coupon.cjs.entry.js +8 -1
  66. package/dist/cjs/salla-notification-item_2.cjs.entry.js +2 -2
  67. package/dist/cjs/salla-offer-modal.cjs.entry.js +1 -1
  68. package/dist/cjs/salla-offer.cjs.entry.js +1 -1
  69. package/dist/cjs/salla-order-branch.cjs.entry.js +1 -1
  70. package/dist/cjs/salla-order-cancel.cjs.entry.js +1 -1
  71. package/dist/cjs/salla-order-details-multiple-bundle-product.cjs.entry.js +1 -1
  72. package/dist/cjs/salla-order-details-options.cjs.entry.js +1 -1
  73. package/dist/cjs/salla-order-details.cjs.entry.js +1 -1
  74. package/dist/cjs/salla-order-edit-item.cjs.entry.js +1 -1
  75. package/dist/cjs/salla-order-edit-product-card.cjs.entry.js +1 -1
  76. package/dist/cjs/salla-order-edit.cjs.entry.js +1 -1
  77. package/dist/cjs/salla-order-items.cjs.entry.js +1 -1
  78. package/dist/cjs/salla-order-options-modal.cjs.entry.js +1 -1
  79. package/dist/cjs/salla-order-payment.cjs.entry.js +1 -1
  80. package/dist/cjs/salla-order-rewards.cjs.entry.js +1 -1
  81. package/dist/cjs/salla-order-shipments.cjs.entry.js +2 -11
  82. package/dist/cjs/salla-order-summary.cjs.entry.js +1 -1
  83. package/dist/cjs/salla-order-totals-card.cjs.entry.js +27 -6
  84. package/dist/cjs/salla-orders.cjs.entry.js +2 -2
  85. package/dist/cjs/salla-payments.cjs.entry.js +1 -1
  86. package/dist/cjs/salla-placeholder.cjs.entry.js +1 -1
  87. package/dist/cjs/salla-price-range.cjs.entry.js +1 -1
  88. package/dist/cjs/salla-product-card-embed.cjs.entry.js +1 -1
  89. package/dist/cjs/salla-product-card.cjs.entry.js +1 -1
  90. package/dist/cjs/salla-product-size-guide.cjs.entry.js +1 -1
  91. package/dist/cjs/salla-products-list.cjs.entry.js +2 -2
  92. package/dist/cjs/salla-products-slider.cjs.entry.js +2 -2
  93. package/dist/cjs/salla-quick-order.cjs.entry.js +1 -1
  94. package/dist/cjs/salla-rating-modal-v2.cjs.entry.js +2 -2
  95. package/dist/cjs/salla-rating-modal.cjs.entry.js +2 -2
  96. package/dist/cjs/salla-review-factors-tags.cjs.entry.js +1 -1
  97. package/dist/cjs/salla-review-order-item.cjs.entry.js +1 -1
  98. package/dist/cjs/salla-scopes.cjs.entry.js +1 -1
  99. package/dist/cjs/salla-search-product-card.cjs.entry.js +1 -1
  100. package/dist/cjs/salla-search.cjs.entry.js +2 -2
  101. package/dist/cjs/salla-skeleton.cjs.entry.js +1 -1
  102. package/dist/cjs/salla-social-share.cjs.entry.js +1 -1
  103. package/dist/cjs/salla-social.cjs.entry.js +1 -1
  104. package/dist/cjs/salla-tab-content_3.cjs.entry.js +1 -1
  105. package/dist/cjs/salla-thank-you-header.cjs.entry.js +1 -1
  106. package/dist/cjs/salla-thank-you-page.cjs.entry.js +1 -1
  107. package/dist/cjs/salla-tiered-offer.cjs.entry.js +1 -1
  108. package/dist/cjs/salla-trust-badges.cjs.entry.js +1 -1
  109. package/dist/cjs/salla-user-menu.cjs.entry.js +1 -1
  110. package/dist/cjs/salla-user-profile.cjs.entry.js +1 -1
  111. package/dist/cjs/salla-user-settings.cjs.entry.js +1 -1
  112. package/dist/cjs/salla-verify.cjs.entry.js +1 -1
  113. package/dist/cjs/salla-wallet.cjs.entry.js +1 -1
  114. package/dist/cjs/salla-wishlist-actions.cjs.entry.js +1 -1
  115. package/dist/cjs/salla-wishlist-summary.cjs.entry.js +2 -2
  116. package/dist/cjs/twilight.cjs.js +2 -2
  117. package/dist/cjs/{vanilla-picker-CceY3g8c.js → vanilla-picker-C9AVuYe1.js} +1 -1
  118. package/dist/collection/components/salla-cart-item-offers/salla-cart-item-offers.js +4 -0
  119. package/dist/collection/components/salla-cart-summary-card/salla-cart-summary-card.css +152 -59
  120. package/dist/collection/components/salla-cart-summary-card/salla-cart-summary-card.js +123 -4
  121. package/dist/collection/components/salla-conditional-offer/salla-conditional-offer.css +139 -0
  122. package/dist/collection/components/salla-conditional-offer/salla-conditional-offer.js +568 -14
  123. package/dist/collection/components/salla-next-order-coupon/salla-next-order-coupon.js +7 -0
  124. package/dist/collection/components/salla-order-shipments/salla-order-shipments.js +1 -10
  125. package/dist/collection/components/salla-order-totals-card/salla-order-totals-card.css +3 -0
  126. package/dist/collection/components/salla-order-totals-card/salla-order-totals-card.js +25 -4
  127. package/dist/collection/shared/cart-free-gifts.js +42 -1
  128. package/dist/collection/shared/offer-stages.js +22 -3
  129. package/dist/components/index.js +2 -2
  130. package/dist/components/info.js +11 -0
  131. package/dist/components/salla-bottom-alert.js +2 -8
  132. package/dist/components/salla-cart-item-offers.js +4 -0
  133. package/dist/components/salla-cart-summary-card.js +124 -5
  134. package/dist/components/salla-conditional-offer.js +631 -18
  135. package/dist/components/salla-next-order-coupon.js +7 -0
  136. package/dist/components/salla-order-shipments.js +1 -10
  137. package/dist/components/salla-order-totals-card.js +26 -5
  138. package/dist/docs.json +4 -4
  139. package/dist/esm/{Helper-BwzPfwZJ.js → Helper-CFEqaSN-.js} +1 -1
  140. package/dist/esm/{filepond-JNV-1zqR.js → filepond-BfW_OXid.js} +1 -1
  141. package/dist/esm/{filepond-plugin-file-poster-nFqNHCag.js → filepond-plugin-file-poster-CsMiL-wZ.js} +1 -1
  142. package/dist/esm/{filepond-plugin-file-validate-size-P3KmOTH8.js → filepond-plugin-file-validate-size-BuxVUFKn.js} +1 -1
  143. package/dist/esm/{filepond-plugin-file-validate-type-BV0Ctr7U.js → filepond-plugin-file-validate-type-Dq94isoe.js} +1 -1
  144. package/dist/esm/{filepond-plugin-image-edit-D62rKlfH.js → filepond-plugin-image-edit-CEVstXlA.js} +1 -1
  145. package/dist/esm/{filepond-plugin-image-exif-orientation-DerapxIk.js → filepond-plugin-image-exif-orientation-BhViRs-X.js} +1 -1
  146. package/dist/esm/{filepond-plugin-image-preview-BlRd2Ble.js → filepond-plugin-image-preview-BrscXNqJ.js} +1 -1
  147. package/dist/esm/{index-CtoXzf1y.js → index-g-_4am8C.js} +6 -6
  148. package/dist/esm/info-OdOOjQZO.js +11 -0
  149. package/dist/esm/{intlTelInput-BZz-u5_w.js → intlTelInput-PsSXBr__.js} +1 -1
  150. package/dist/esm/loader.js +3 -3
  151. package/dist/esm/salla-accordion-body_2.entry.js +1 -1
  152. package/dist/esm/salla-accordion_6.entry.js +2 -2
  153. package/dist/esm/salla-add-product-button_5.entry.js +2 -2
  154. package/dist/esm/salla-advertisement.entry.js +1 -1
  155. package/dist/esm/salla-alert_2.entry.js +3 -9
  156. package/dist/esm/salla-app-install-alert.entry.js +1 -1
  157. package/dist/esm/salla-applied-filters.entry.js +2 -2
  158. package/dist/esm/salla-apps-icons.entry.js +1 -1
  159. package/dist/esm/salla-attachment-view_9.entry.js +11 -11
  160. package/dist/esm/salla-badge.entry.js +1 -1
  161. package/dist/esm/salla-basket-gap.entry.js +1 -1
  162. package/dist/esm/salla-bullet-delivery_3.entry.js +1 -1
  163. package/dist/esm/salla-cart-coupons.entry.js +1 -1
  164. package/dist/esm/salla-cart-item-offers_2.entry.js +5 -1
  165. package/dist/esm/salla-cart-summary-card.entry.js +120 -5
  166. package/dist/esm/salla-cashback-banner.entry.js +1 -1
  167. package/dist/esm/salla-comment-factors.entry.js +1 -1
  168. package/dist/esm/salla-comment-form_9.entry.js +2 -2
  169. package/dist/esm/salla-conditional-offer.entry.js +628 -19
  170. package/dist/esm/salla-contacts.entry.js +1 -1
  171. package/dist/esm/salla-cookies-bar.entry.js +1 -1
  172. package/dist/esm/salla-count-down.entry.js +1 -1
  173. package/dist/esm/salla-custom-fields.entry.js +1 -1
  174. package/dist/esm/salla-delivery-promise.entry.js +1 -1
  175. package/dist/esm/salla-edit-order-button.entry.js +1 -1
  176. package/dist/esm/salla-filters-widget.entry.js +2 -2
  177. package/dist/esm/salla-filters.entry.js +2 -2
  178. package/dist/esm/salla-free-shipping-banner.entry.js +1 -1
  179. package/dist/esm/salla-free-shipping-tag.entry.js +1 -1
  180. package/dist/esm/salla-fulfillment-methods.entry.js +1 -1
  181. package/dist/esm/salla-gift-card-redeem_2.entry.js +1 -1
  182. package/dist/esm/salla-gifting.entry.js +1 -1
  183. package/dist/esm/salla-hook.entry.js +1 -1
  184. package/dist/esm/salla-infinite-scroll.entry.js +2 -2
  185. package/dist/esm/salla-installment.entry.js +1 -1
  186. package/dist/esm/salla-list-tile.entry.js +1 -1
  187. package/dist/esm/salla-localization-modal.entry.js +1 -1
  188. package/dist/esm/salla-login-modal.entry.js +1 -1
  189. package/dist/esm/salla-loyalty-banner_10.entry.js +1 -1
  190. package/dist/esm/salla-loyalty-exchange-product-card.entry.js +1 -1
  191. package/dist/esm/salla-loyalty-points-banner.entry.js +1 -1
  192. package/dist/esm/salla-loyalty-points-history.entry.js +1 -1
  193. package/dist/esm/salla-loyalty-prize-item.entry.js +1 -1
  194. package/dist/esm/salla-loyalty.entry.js +1 -1
  195. package/dist/esm/salla-maintenance-alert.entry.js +1 -1
  196. package/dist/esm/salla-map_3.entry.js +1 -1
  197. package/dist/esm/salla-menu.entry.js +1 -1
  198. package/dist/esm/salla-metadata.entry.js +1 -1
  199. package/dist/esm/salla-mobile-update-modal.entry.js +1 -1
  200. package/dist/esm/salla-multiple-bundle-product-cart_2.entry.js +1 -1
  201. package/dist/esm/salla-multiple-bundle-product-options-modal_2.entry.js +1 -1
  202. package/dist/esm/salla-multiple-bundle-product.entry.js +1 -1
  203. package/dist/esm/salla-next-order-coupon.entry.js +8 -1
  204. package/dist/esm/salla-notification-item_2.entry.js +2 -2
  205. package/dist/esm/salla-offer-modal.entry.js +1 -1
  206. package/dist/esm/salla-offer.entry.js +1 -1
  207. package/dist/esm/salla-order-branch.entry.js +1 -1
  208. package/dist/esm/salla-order-cancel.entry.js +1 -1
  209. package/dist/esm/salla-order-details-multiple-bundle-product.entry.js +1 -1
  210. package/dist/esm/salla-order-details-options.entry.js +1 -1
  211. package/dist/esm/salla-order-details.entry.js +1 -1
  212. package/dist/esm/salla-order-edit-item.entry.js +1 -1
  213. package/dist/esm/salla-order-edit-product-card.entry.js +1 -1
  214. package/dist/esm/salla-order-edit.entry.js +1 -1
  215. package/dist/esm/salla-order-items.entry.js +1 -1
  216. package/dist/esm/salla-order-options-modal.entry.js +1 -1
  217. package/dist/esm/salla-order-payment.entry.js +1 -1
  218. package/dist/esm/salla-order-rewards.entry.js +1 -1
  219. package/dist/esm/salla-order-shipments.entry.js +2 -11
  220. package/dist/esm/salla-order-summary.entry.js +1 -1
  221. package/dist/esm/salla-order-totals-card.entry.js +27 -6
  222. package/dist/esm/salla-orders.entry.js +2 -2
  223. package/dist/esm/salla-payments.entry.js +1 -1
  224. package/dist/esm/salla-placeholder.entry.js +1 -1
  225. package/dist/esm/salla-price-range.entry.js +1 -1
  226. package/dist/esm/salla-product-card-embed.entry.js +1 -1
  227. package/dist/esm/salla-product-card.entry.js +1 -1
  228. package/dist/esm/salla-product-size-guide.entry.js +1 -1
  229. package/dist/esm/salla-products-list.entry.js +2 -2
  230. package/dist/esm/salla-products-slider.entry.js +2 -2
  231. package/dist/esm/salla-quick-order.entry.js +1 -1
  232. package/dist/esm/salla-rating-modal-v2.entry.js +2 -2
  233. package/dist/esm/salla-rating-modal.entry.js +2 -2
  234. package/dist/esm/salla-review-factors-tags.entry.js +1 -1
  235. package/dist/esm/salla-review-order-item.entry.js +1 -1
  236. package/dist/esm/salla-scopes.entry.js +1 -1
  237. package/dist/esm/salla-search-product-card.entry.js +1 -1
  238. package/dist/esm/salla-search.entry.js +2 -2
  239. package/dist/esm/salla-skeleton.entry.js +1 -1
  240. package/dist/esm/salla-social-share.entry.js +1 -1
  241. package/dist/esm/salla-social.entry.js +1 -1
  242. package/dist/esm/salla-tab-content_3.entry.js +1 -1
  243. package/dist/esm/salla-thank-you-header.entry.js +1 -1
  244. package/dist/esm/salla-thank-you-page.entry.js +1 -1
  245. package/dist/esm/salla-tiered-offer.entry.js +1 -1
  246. package/dist/esm/salla-trust-badges.entry.js +1 -1
  247. package/dist/esm/salla-user-menu.entry.js +1 -1
  248. package/dist/esm/salla-user-profile.entry.js +1 -1
  249. package/dist/esm/salla-user-settings.entry.js +1 -1
  250. package/dist/esm/salla-verify.entry.js +1 -1
  251. package/dist/esm/salla-wallet.entry.js +1 -1
  252. package/dist/esm/salla-wishlist-actions.entry.js +1 -1
  253. package/dist/esm/salla-wishlist-summary.entry.js +2 -2
  254. package/dist/esm/twilight.js +3 -3
  255. package/dist/esm/{vanilla-picker-BfmpXY4G.js → vanilla-picker-D3lgt9go.js} +1 -1
  256. package/dist/twilight/{p-0b583810.entry.js → p-02006b3a.entry.js} +1 -1
  257. package/dist/twilight/{p-f51f31df.entry.js → p-0207dae4.entry.js} +1 -1
  258. package/dist/twilight/{p-117e714b.entry.js → p-025e30c7.entry.js} +1 -1
  259. package/dist/twilight/{p-38b70281.entry.js → p-058e4be3.entry.js} +1 -1
  260. package/dist/twilight/{p-77deaa00.entry.js → p-0ce6514e.entry.js} +1 -1
  261. package/dist/twilight/{p-f3455825.entry.js → p-0d94c90e.entry.js} +1 -1
  262. package/dist/twilight/{p-5dea6c09.entry.js → p-0e423424.entry.js} +1 -1
  263. package/dist/twilight/{p-71b5546b.entry.js → p-0f7db4a1.entry.js} +1 -1
  264. package/dist/twilight/{p-374d9f0c.entry.js → p-12038aef.entry.js} +1 -1
  265. package/dist/twilight/{p-2e1dd9cf.entry.js → p-12dcb9a0.entry.js} +1 -1
  266. package/dist/twilight/{p-aac9d2fe.entry.js → p-14311053.entry.js} +1 -1
  267. package/dist/twilight/{p-0ce0cdb0.entry.js → p-151df172.entry.js} +1 -1
  268. package/dist/twilight/{p-d76b7564.entry.js → p-152ac425.entry.js} +1 -1
  269. package/dist/twilight/{p-a4fee391.entry.js → p-1b6d84a8.entry.js} +1 -1
  270. package/dist/twilight/p-1d31b983.entry.js +4 -0
  271. package/dist/twilight/{p-6495383e.entry.js → p-1dcbbbbf.entry.js} +1 -1
  272. package/dist/twilight/{p-f310b923.entry.js → p-1df3fe4a.entry.js} +1 -1
  273. package/dist/twilight/{p-00a1ec4e.entry.js → p-22271248.entry.js} +1 -1
  274. package/dist/twilight/{p-e8ebb2fe.entry.js → p-2834a4d3.entry.js} +1 -1
  275. package/dist/twilight/{p-2882d626.entry.js → p-2907057d.entry.js} +1 -1
  276. package/dist/twilight/{p-ed37e702.entry.js → p-2d3b29d9.entry.js} +1 -1
  277. package/dist/twilight/{p-055bf228.entry.js → p-2db9ee05.entry.js} +1 -1
  278. package/dist/twilight/{p-4080fdc9.entry.js → p-2e4ce75b.entry.js} +1 -1
  279. package/dist/twilight/{p-3845e427.entry.js → p-329b7cc2.entry.js} +1 -1
  280. package/dist/twilight/{p-c4264e87.entry.js → p-3c2e7bef.entry.js} +1 -1
  281. package/dist/twilight/p-3fedd652.entry.js +4 -0
  282. package/dist/twilight/{p-36a7344c.entry.js → p-4247db1a.entry.js} +1 -1
  283. package/dist/twilight/{p-f664fa69.entry.js → p-496f950b.entry.js} +1 -1
  284. package/dist/twilight/{p-40e651ac.entry.js → p-49cd89d1.entry.js} +1 -1
  285. package/dist/twilight/p-4b8bb969.entry.js +4 -0
  286. package/dist/twilight/{p-c18fe9e5.entry.js → p-5166cd84.entry.js} +1 -1
  287. package/dist/twilight/{p-cbff6c68.entry.js → p-5350ba07.entry.js} +1 -1
  288. package/dist/twilight/{p-2c04bba3.entry.js → p-54eec74a.entry.js} +1 -1
  289. package/dist/twilight/{p-ae1efde9.entry.js → p-55861492.entry.js} +1 -1
  290. package/dist/twilight/{p-cd55369c.entry.js → p-6070b995.entry.js} +1 -1
  291. package/dist/twilight/{p-4254e9c2.entry.js → p-62a3394a.entry.js} +1 -1
  292. package/dist/twilight/{p-974c100d.entry.js → p-63c982e1.entry.js} +1 -1
  293. package/dist/twilight/{p-2afc4658.entry.js → p-6432401e.entry.js} +1 -1
  294. package/dist/twilight/{p-7a8fde62.entry.js → p-64ae09b4.entry.js} +1 -1
  295. package/dist/twilight/{p-f67d03fb.entry.js → p-685a61fa.entry.js} +1 -1
  296. package/dist/twilight/{p-7c684782.entry.js → p-710a42d2.entry.js} +1 -1
  297. package/dist/twilight/{p-2566f688.entry.js → p-72e13a12.entry.js} +1 -1
  298. package/dist/twilight/{p-ebed682b.entry.js → p-73612f04.entry.js} +1 -1
  299. package/dist/twilight/{p-2401f0d7.entry.js → p-74602eb0.entry.js} +1 -1
  300. package/dist/twilight/{p-365ff361.entry.js → p-76e238b3.entry.js} +1 -1
  301. package/dist/twilight/{p-7169ba9a.entry.js → p-78240cb3.entry.js} +1 -1
  302. package/dist/twilight/{p-8413d52b.entry.js → p-78a7b46e.entry.js} +1 -1
  303. package/dist/twilight/{p-1af75625.entry.js → p-79f9403a.entry.js} +1 -1
  304. package/dist/twilight/{p-6ffbf505.entry.js → p-7f4f300a.entry.js} +1 -1
  305. package/dist/twilight/{p-2140be84.entry.js → p-7fc9a0bf.entry.js} +1 -1
  306. package/dist/twilight/{p-b1bc70af.entry.js → p-8016f122.entry.js} +1 -1
  307. package/dist/twilight/{p-a8102290.entry.js → p-801eb7d8.entry.js} +1 -1
  308. package/dist/twilight/{p-02ddd64c.entry.js → p-81ec67d8.entry.js} +1 -1
  309. package/dist/twilight/{p-3356638d.entry.js → p-83a1123b.entry.js} +1 -1
  310. package/dist/twilight/p-84b9416e.entry.js +4 -0
  311. package/dist/twilight/{p-8fe950e8.entry.js → p-8783aa81.entry.js} +1 -1
  312. package/dist/twilight/{p-60ae422e.entry.js → p-87ba0ca2.entry.js} +1 -1
  313. package/dist/twilight/{p-e2cb5c49.entry.js → p-8cbbe961.entry.js} +1 -1
  314. package/dist/twilight/{p-a260f35e.entry.js → p-8ec7866e.entry.js} +1 -1
  315. package/dist/twilight/{p-e89daada.entry.js → p-92cbac49.entry.js} +1 -1
  316. package/dist/twilight/{p-3da2e3f3.entry.js → p-93a85463.entry.js} +1 -1
  317. package/dist/twilight/{p-4f4933f8.entry.js → p-95f8caa0.entry.js} +1 -1
  318. package/dist/twilight/{p-061216d4.entry.js → p-965c3ee8.entry.js} +1 -1
  319. package/dist/twilight/{p-9c1b0978.entry.js → p-98b24f1d.entry.js} +1 -1
  320. package/dist/twilight/{p-6KZIgnh6.js → p-9h9-vkr9.js} +1 -1
  321. package/dist/twilight/{p-DT73iyFr.js → p-B6WlUws-.js} +1 -1
  322. package/dist/twilight/{p-78r0jdyY.js → p-ByR59DTl.js} +2 -2
  323. package/dist/twilight/{p-DYAn-F48.js → p-CB552NRi.js} +2 -2
  324. package/dist/twilight/{p-x4_Sxqd_.js → p-CWi90iJ1.js} +1 -1
  325. package/dist/twilight/{p-CC-KFXD9.js → p-DjCyIWR1.js} +1 -1
  326. package/dist/twilight/{p-BeC8AhMn.js → p-EXd4paYi.js} +1 -1
  327. package/dist/twilight/{p-BwUX8dew.js → p-MEqjtc_i.js} +1 -1
  328. package/dist/twilight/p-OdOOjQZO.js +4 -0
  329. package/dist/twilight/{p-D5DrmlyQ.js → p-WsiD2WP-.js} +1 -1
  330. package/dist/twilight/{p-F1Erq7GR.js → p-_ulhlUUw.js} +1 -1
  331. package/dist/twilight/{p-b3472096.entry.js → p-a185d836.entry.js} +1 -1
  332. package/dist/twilight/p-a2147ef3.entry.js +4 -0
  333. package/dist/twilight/{p-1d105a57.entry.js → p-a21bc7ee.entry.js} +1 -1
  334. package/dist/twilight/{p-1f39ca4f.entry.js → p-a255167f.entry.js} +1 -1
  335. package/dist/twilight/{p-72b0724c.entry.js → p-a76a2a5d.entry.js} +1 -1
  336. package/dist/twilight/{p-97065b4f.entry.js → p-a7aa162c.entry.js} +1 -1
  337. package/dist/twilight/{p-c1ab1b40.entry.js → p-aa5be7cf.entry.js} +1 -1
  338. package/dist/twilight/{p-0d1b8490.entry.js → p-ac31a382.entry.js} +1 -1
  339. package/dist/twilight/{p-94d5b080.entry.js → p-ad95bb9a.entry.js} +1 -1
  340. package/dist/twilight/{p-0cb60522.entry.js → p-ae4ea8c9.entry.js} +1 -1
  341. package/dist/twilight/{p-d6a3b2d8.entry.js → p-ba601791.entry.js} +1 -1
  342. package/dist/twilight/{p-dc2be7b8.entry.js → p-c0423cfc.entry.js} +1 -1
  343. package/dist/twilight/{p-a236dd3b.entry.js → p-c861b955.entry.js} +1 -1
  344. package/dist/twilight/{p-3d3eda0f.entry.js → p-c96e9150.entry.js} +1 -1
  345. package/dist/twilight/{p-1b4c3b85.entry.js → p-cdd8e44a.entry.js} +1 -1
  346. package/dist/twilight/{p-9e50f29c.entry.js → p-cf3285f1.entry.js} +1 -1
  347. package/dist/twilight/p-d02dee6d.entry.js +4 -0
  348. package/dist/twilight/{p-95e7c2e1.entry.js → p-d204326c.entry.js} +1 -1
  349. package/dist/twilight/{p-e5dc1d27.entry.js → p-d304b8fc.entry.js} +1 -1
  350. package/dist/twilight/{p-79b743eb.entry.js → p-da63cbf0.entry.js} +1 -1
  351. package/dist/twilight/p-db2b9499.entry.js +4 -0
  352. package/dist/twilight/{p-a9ae6ab9.entry.js → p-dba5b89a.entry.js} +1 -1
  353. package/dist/twilight/{p-aca2717e.entry.js → p-e0387b79.entry.js} +1 -1
  354. package/dist/twilight/{p-f738f492.entry.js → p-e0dc2c2d.entry.js} +1 -1
  355. package/dist/twilight/{p-3e285056.entry.js → p-e3d37ab2.entry.js} +1 -1
  356. package/dist/twilight/{p-b3c9ab0e.entry.js → p-e59a8901.entry.js} +1 -1
  357. package/dist/twilight/{p-4adaee32.entry.js → p-e776afcd.entry.js} +1 -1
  358. package/dist/twilight/{p-4008e164.entry.js → p-e8921db3.entry.js} +1 -1
  359. package/dist/twilight/p-ec9434d9.entry.js +4 -0
  360. package/dist/twilight/{p-4c5a7ffc.entry.js → p-ee7e6ce9.entry.js} +1 -1
  361. package/dist/twilight/{p-213368ef.entry.js → p-f0ab3933.entry.js} +1 -1
  362. package/dist/twilight/{p-8ea3a265.entry.js → p-f16bdaf5.entry.js} +1 -1
  363. package/dist/twilight/{p-e8198efc.entry.js → p-f2718aec.entry.js} +1 -1
  364. package/dist/twilight/{p-118aa92e.entry.js → p-f581bb32.entry.js} +1 -1
  365. package/dist/twilight/{p-8fd6ed52.entry.js → p-f6981856.entry.js} +1 -1
  366. package/dist/twilight/{p-b2cbdaa0.entry.js → p-f7f8d758.entry.js} +1 -1
  367. package/dist/twilight/{p-fbce8a18.entry.js → p-fbff3efd.entry.js} +1 -1
  368. package/dist/twilight/{p-36167cbe.entry.js → p-ff890108.entry.js} +1 -1
  369. package/dist/twilight/{p-8cbf3bc2.entry.js → p-fffd562d.entry.js} +1 -1
  370. package/dist/twilight/{p-CtoXzf1y.js → p-g-_4am8C.js} +1 -1
  371. package/dist/twilight/twilight.esm.js +1 -1
  372. package/dist/types/components/salla-cart-summary-card/salla-cart-summary-card.d.ts +26 -0
  373. package/dist/types/components/salla-conditional-offer/interfaces.d.ts +36 -0
  374. package/dist/types/components/salla-conditional-offer/salla-conditional-offer.d.ts +182 -3
  375. package/dist/types/components/salla-order-shipments/salla-order-shipments.d.ts +0 -1
  376. package/dist/types/components/salla-order-totals-card/interfaces.d.ts +8 -3
  377. package/dist/types/components/salla-order-totals-card/salla-order-totals-card.d.ts +2 -1
  378. package/dist/types/shared/cart-free-gifts.d.ts +26 -0
  379. package/dist/types/shared/offer-stages.d.ts +6 -1
  380. package/package.json +5 -5
  381. package/dist/twilight/p-034b80ad.entry.js +0 -4
  382. package/dist/twilight/p-0fb390fb.entry.js +0 -4
  383. package/dist/twilight/p-17be7967.entry.js +0 -4
  384. package/dist/twilight/p-5bc2190a.entry.js +0 -4
  385. package/dist/twilight/p-7303ebd1.entry.js +0 -4
  386. package/dist/twilight/p-8b9c44c8.entry.js +0 -4
  387. package/dist/twilight/p-919650b7.entry.js +0 -4
  388. package/dist/twilight/p-ebe45961.entry.js +0 -4
@@ -3,7 +3,7 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
- var index = require('./index-BJ3-fFSF.js');
6
+ var index = require('./index-BLPUgwXq.js');
7
7
 
8
8
  var DiscountType;
9
9
  (function (DiscountType) {
@@ -46,6 +46,9 @@ var OfferType;
46
46
  * `twilight/packages/twilight/src/api/cart.js:831-846`.
47
47
  */
48
48
  const EMPTY_GIFT_ITEMS = Object.freeze([]);
49
+ const EMPTY_PRODUCT_LINES = Object.freeze([]);
50
+ /** Every line from the last parsed response — published in the SAME pass as `itemsCache`. */
51
+ let linesCache = null;
49
52
  /**
50
53
  * The gift LINES from the last parsed response. `null` is the single "nothing has loaded
51
54
  * yet" flag shared with `countCache`, which keeps the two from ever disagreeing about
@@ -147,6 +150,7 @@ function syncCartIdentity() {
147
150
  if (cacheIdentity !== undefined && cacheIdentity !== current) {
148
151
  generation++;
149
152
  itemsCache = null;
153
+ linesCache = null;
150
154
  countCache = null;
151
155
  isAuthoritative = false;
152
156
  pendingRequest = null;
@@ -271,6 +275,24 @@ function buildGiftItems(items) {
271
275
  }
272
276
  return gifts;
273
277
  }
278
+ /** Every line of the cart, in cart order — see `CartProductLine`. */
279
+ function buildProductLines(items) {
280
+ const lines = [];
281
+ for (const item of items) {
282
+ if (item?.id === undefined || item?.id === null)
283
+ continue;
284
+ const quantity = toNumber(item.quantity);
285
+ lines.push({
286
+ id: String(item.id),
287
+ productId: toNullableNumber(item.product_id),
288
+ quantity,
289
+ // Only a line the server flagged as granted by a cart offer has free units.
290
+ freeQuantity: item.added_by_offer ? Math.max(0, resolveFreeQuantity(item, quantity)) : 0,
291
+ offerId: item.added_by_offer ? toNullableNumber(item.offer_id) : null,
292
+ });
293
+ }
294
+ return lines;
295
+ }
274
296
  function fetchAndCache() {
275
297
  if (pendingRequest)
276
298
  return pendingRequest;
@@ -285,6 +307,7 @@ function fetchAndCache() {
285
307
  // Both projections come out of the SAME response, in one pass.
286
308
  return {
287
309
  gifts: buildGiftItems(items),
310
+ lines: buildProductLines(items),
288
311
  count: extractCartCount(res, found),
289
312
  // The response was reached and parsed — these values are the cart, so the cache
290
313
  // they land in may be short-circuited on. See `isAuthoritative`.
@@ -304,6 +327,7 @@ function fetchAndCache() {
304
327
  // connection can never claim the cart is empty.
305
328
  .catch((error) => ({
306
329
  gifts: itemsCache ?? [],
330
+ lines: linesCache ?? [],
307
331
  count: isCartNotCreated(error) ? 0 : countCache,
308
332
  /**
309
333
  * NOT authoritative, `CartNotCreated` included.
@@ -320,13 +344,14 @@ function fetchAndCache() {
320
344
  */
321
345
  ok: false,
322
346
  }))
323
- .then(({ gifts, count, ok }) => {
347
+ .then(({ gifts, lines, count, ok }) => {
324
348
  // Only publish if nothing invalidated us while we were in flight.
325
349
  // Otherwise this is pre-mutation data racing a newer, authoritative
326
350
  // request — publishing it would make the lookups short-circuit on stale
327
351
  // data until the NEXT mutation happens to come along.
328
352
  if (startedAtGeneration === generation) {
329
353
  itemsCache = gifts;
354
+ linesCache = lines;
330
355
  countCache = count;
331
356
  isAuthoritative = ok;
332
357
  }
@@ -355,6 +380,20 @@ function getCartFreeGiftItems() {
355
380
  }
356
381
  return fetchAndCache().then(() => itemsCache ?? EMPTY_GIFT_ITEMS);
357
382
  }
383
+ /**
384
+ * Every line of the cart (paid and free), in cart order — for the CP-2171 free product picker.
385
+ *
386
+ * Shares the cache, the in-flight promise and the generation guard with
387
+ * `getCartFreeGiftItems()` and `getCartItemCount()`: no request of its own, and never a
388
+ * different cart state from theirs.
389
+ */
390
+ function getCartProductLines() {
391
+ syncCartIdentity();
392
+ if (isAuthoritative && itemsCache) {
393
+ return Promise.resolve(linesCache ?? EMPTY_PRODUCT_LINES);
394
+ }
395
+ return fetchAndCache().then(() => linesCache ?? EMPTY_PRODUCT_LINES);
396
+ }
358
397
  /**
359
398
  * Whether the cached cart picture came from a response that was actually reached and
360
399
  * parsed, rather than from the fallback a rejected lookup publishes.
@@ -427,6 +466,7 @@ function refreshCartFreeGifts() {
427
466
  return pendingRefresh;
428
467
  generation++;
429
468
  itemsCache = null;
469
+ linesCache = null;
430
470
  countCache = null;
431
471
  isAuthoritative = false;
432
472
  // This invalidation already covers whatever id the cart has now.
@@ -665,8 +705,13 @@ function sortByThreshold(items, getThreshold, getIdentity) {
665
705
  * @param rawTierValues each stage's OWN `current_value`, aligned the same way. A tier
666
706
  * without a readable one is measured by `currentValue`, so omitting it — an older
667
707
  * backend — changes nothing. See `OfferStageProgress.values`.
708
+ * @param rawCanAddFree each stage's `can_add_free` (CP-2171), aligned the same way. Read
709
+ * only in case (3), where no tier carries `is_applied`: a free product tier that
710
+ * carries one is reached when the ENGINE says so, not when its `current_value` —
711
+ * which counts the gift lines at full price — passes `min_spend`. Omit it and case
712
+ * (3) is the threshold behaviour, unchanged.
668
713
  */
669
- function computeOfferStageProgress(currentValue, rawThresholds, rawStartValue = 0, rawApplied = [], rawTierValues = []) {
714
+ function computeOfferStageProgress(currentValue, rawThresholds, rawStartValue = 0, rawApplied = [], rawTierValues = [], rawCanAddFree = []) {
670
715
  // Unparseable thresholds are dropped, not zeroed — see `isUsableThreshold`.
671
716
  //
672
717
  // The flag travels WITH its threshold through both the drop and the sort. Sorting two
@@ -679,6 +724,7 @@ function computeOfferStageProgress(currentValue, rawThresholds, rawStartValue =
679
724
  threshold: parseNumeric(threshold),
680
725
  applied: readAppliedFlag(rawApplied?.[index]),
681
726
  tierValue: parseNumeric(rawTierValues?.[index]),
727
+ canAddFree: typeof rawCanAddFree?.[index] === 'boolean' ? rawCanAddFree[index] : undefined,
682
728
  }))
683
729
  .filter((entry) => entry.threshold !== null)
684
730
  .sort((a, b) => a.threshold - b.threshold || a.index - b.index);
@@ -766,12 +812,25 @@ function computeOfferStageProgress(currentValue, rawThresholds, rawStartValue =
766
812
  appliedIndex = index;
767
813
  }
768
814
  }
769
- // Case (3) closes the prefix at `lastReachedIndex` rather than comparing tier by tier.
815
+ // Case (3) closes the prefix at the last reached tier rather than comparing tier by tier.
770
816
  // With one value for every tier the two are the same statement; with per-tier values
771
817
  // it is what keeps `met` a prefix, on the same single-winner reasoning as case (1).
818
+ //
819
+ // CP-2171 — a tier that carries `can_add_free` is reached on the engine's answer, not on
820
+ // its value: a guest with 40 paid and three listed gifts in the cart has a value past
821
+ // `min_spend`, yet every gift is charged. Only `met` reads it; the bar (`lastReachedIndex`,
822
+ // `fills`, `isComplete`) stays the threshold projection, and the guidance line, finding
823
+ // nothing left to spend, says the conditions are not met.
824
+ let lastMetIndex = NO_STAGE_REACHED;
825
+ for (let index = 0; index < thresholds.length; index++) {
826
+ const canAddFree = entries[index].canAddFree;
827
+ if (canAddFree === undefined ? values[index] >= thresholds[index] : canAddFree) {
828
+ lastMetIndex = index;
829
+ }
830
+ }
772
831
  const met = hasEngineVerdict
773
832
  ? thresholds.map((_, index) => index <= appliedIndex)
774
- : thresholds.map((_, index) => index <= lastReachedIndex);
833
+ : thresholds.map((_, index) => index <= lastMetIndex);
775
834
  const metCount = met.filter(Boolean).length;
776
835
  const nextUnmetIndex = met.findIndex(isMet => !isMet); // -1 === NO_STAGE_REACHED
777
836
  const allMet = nextUnmetIndex === NO_STAGE_REACHED;
@@ -919,7 +978,7 @@ function getRemainingToStage(index, progress, measure = OfferStageMeasure.CURREN
919
978
  return rounded > 0 ? rounded : 1 / factor;
920
979
  }
921
980
 
922
- const sallaConditionalOfferCss = "/*!twlt*/:host{display:block}.s-conditional-offer-root{background-color:rgba(0, 137, 123, 0.06);background-color:color-mix(in srgb, var(--color-primary, #00897b) 7%, transparent)}.s-conditional-offer-badges::-webkit-scrollbar{height:3px}.s-conditional-offer-badges::-webkit-scrollbar-thumb{border-radius:9999px;background-color:rgba(0, 0, 0, 0.15)}.s-conditional-offer-badges::-webkit-scrollbar-track{background-color:transparent}.s-conditional-offer-tooltip{--s-conditional-offer-tooltip-shift:0px;--s-conditional-offer-tooltip-caret:0px;transform:translateX(calc(-50% + var(--s-conditional-offer-tooltip-shift)))}.s-conditional-offer-tooltip::before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translateX(calc(-50% + var(--s-conditional-offer-tooltip-caret)));border:8px solid transparent;border-bottom-color:#fff}.s-conditional-offer-tier-icon-met,.s-conditional-offer-tier-icon-unmet{color:var(--color-primary-dark, #00695c);background-color:rgba(0, 137, 123, 0.12);background-color:color-mix(in srgb, var(--color-primary, #00897b) 14%, transparent)}.s-conditional-offer-tier:not(:last-child)::before{content:\"\";position:absolute;top:2.5rem;bottom:-1rem;inset-inline-start:1.125rem;width:1px;background-color:#d1d5db}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.w-6{width:1.5rem}.resize{resize:both}.gap-1{gap:0.25rem}.rounded{border-radius:var(--s-radius, 0.25rem)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.s-conditional-offer-root{margin-bottom:1.25rem;display:block;width:100%;min-width:0px;max-width:100%;border-radius:0.375rem;padding:0.75rem}@media (min-width: 768px){.s-conditional-offer-root{padding:1rem}}.s-conditional-offer-banner{display:flex;width:100%;min-width:0px;align-items:center;justify-content:space-between;gap:0.75rem}@media (min-width: 768px){.s-conditional-offer-banner{gap:1rem}}.s-conditional-offer-lead{display:flex;min-width:0px;flex:1 1 0%;align-items:center;gap:0.5rem}@media (min-width: 768px){.s-conditional-offer-lead{gap:0.75rem}}.s-conditional-offer-badges{display:flex;max-width:45%;flex-shrink:0;flex-wrap:nowrap;align-items:center;gap:0.25rem;overflow-x:auto;overscroll-behavior-x:contain;scroll-behavior:smooth;scrollbar-width:thin}.s-conditional-offer-badge{display:inline-flex;height:1.5rem;width:1.5rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;font-size:11px;line-height:1;--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.s-conditional-offer-badge-met{background-color:var(--color-primary)}.s-conditional-offer-badge-unmet{--tw-bg-opacity:1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.s-conditional-offer-badge-link{width:0.5rem;flex-shrink:0;border-top-width:1px}.s-conditional-offer-badge-link-met{border-color:var(--color-primary)}.s-conditional-offer-badge-link-unmet{border-style:dashed;--tw-border-opacity:1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.s-conditional-offer-badge-icon{height:0.75rem;width:0.75rem;flex-shrink:0}.s-conditional-offer-text{display:flex;min-width:0px;flex-direction:column;gap:0.125rem;text-align:start}.s-conditional-offer-heading{display:flex;min-width:0px;align-items:center;gap:0.375rem}.s-conditional-offer-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.75rem;line-height:1rem;--tw-text-opacity:1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.s-conditional-offer-info{position:relative;display:inline-flex;flex-shrink:0;align-items:center}.s-conditional-offer-info-trigger{display:inline-flex;height:1rem;width:1rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;--tw-text-opacity:1;color:rgb(115 115 115 / var(--tw-text-opacity, 1));transition-property:color, background-color, border-color, text-decoration-color, fill, stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms}.s-conditional-offer-info-trigger:hover{color:var(--color-primary-dark)}.s-conditional-offer-info-icon{height:0.875rem;width:0.875rem;flex-shrink:0}.s-conditional-offer-message{font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark)}@media (min-width: 768px){.s-conditional-offer-message{font-size:1rem;line-height:1.5rem}}.s-conditional-offer-toggle{align-items:center;justify-content:center;gap:0.375rem;font-size:0.875rem;line-height:1.25rem;--tw-text-opacity:1;color:rgb(38 38 38 / var(--tw-text-opacity, 1));transition-property:color, background-color, border-color, text-decoration-color, fill, stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms}.s-conditional-offer-toggle:hover{color:var(--color-primary-dark)}.s-conditional-offer-toggle-inline{display:none;flex-shrink:0}@media (min-width: 768px){.s-conditional-offer-toggle-inline{display:inline-flex}}.s-conditional-offer-toggle-bar{margin-top:0.5rem;display:inline-flex;width:100%;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));padding-left:0.75rem;padding-right:0.75rem;padding-top:0.625rem;padding-bottom:0.625rem}@media (min-width: 768px){.s-conditional-offer-toggle-bar{display:none}}.s-conditional-offer-toggle-icon{font-size:1rem;line-height:1.5rem;line-height:1;transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms}.s-conditional-offer-toggle-icon-open{--tw-rotate:180deg;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.s-conditional-offer-panel{margin-top:0.75rem;width:100%;min-width:0px;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));padding:0.75rem}.s-conditional-offer-gifts-header{margin-bottom:0.75rem;display:flex;align-items:center;gap:0.5rem}.s-conditional-offer-gifts-icon{display:inline-flex;height:2rem;width:2rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;border-width:1px;border-color:var(--color-primary);font-size:1rem;line-height:1.5rem;line-height:1;color:var(--color-primary)}.s-conditional-offer-gifts-title{font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-gifts-list{margin:0px;display:flex;list-style-type:none;flex-direction:column;gap:0.5rem;padding:0px}.s-conditional-offer-gift{display:flex;align-items:center;gap:0.75rem;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));padding:0.5rem}.s-conditional-offer-gift-thumb{display:inline-flex;height:3.5rem;width:3.5rem;flex-shrink:0;align-items:center;justify-content:center;overflow:hidden;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));font-size:1.25rem;line-height:1.75rem;--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.s-conditional-offer-gift-image{height:100%;width:100%;object-fit:contain}.s-conditional-offer-gift-body{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:0.125rem;text-align:start}.s-conditional-offer-gift-name{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;font-size:0.875rem;line-height:1.25rem;font-weight:700;--tw-text-opacity:1;color:rgb(38 38 38 / var(--tw-text-opacity, 1))}.s-conditional-offer-gift-prices{display:flex;align-items:center;gap:0.5rem;font-size:0.75rem;line-height:1rem}.s-conditional-offer-gift-free{font-weight:700;--tw-text-opacity:1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.s-conditional-offer-gift-price{--tw-text-opacity:1;color:rgb(115 115 115 / var(--tw-text-opacity, 1));text-decoration-line:line-through}.s-conditional-offer-tooltip{position:absolute;top:calc(100% + 0.75rem);left:50%;z-index:10;width:min(22rem, calc(100vw - 2rem));border-radius:0.5rem;--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));padding:1rem;text-align:start;--tw-shadow:5px 10px 30px #2B2D340D;--tw-shadow-colored:5px 10px 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.s-conditional-offer-tooltip-title{font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-tooltip-description{margin-top:0.25rem;font-size:0.75rem;line-height:1rem;line-height:1.625;--tw-text-opacity:1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.s-conditional-offer-tooltip-list{margin:0px;margin-top:0.75rem;display:flex;list-style-type:none;flex-direction:column;gap:1rem;padding:0px}.s-conditional-offer-tier{position:relative;display:flex;align-items:flex-start;gap:0.75rem}.s-conditional-offer-tier-icon{position:relative;display:inline-flex;height:2.25rem;width:2.25rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;font-size:1rem;line-height:1.5rem;line-height:1}.s-conditional-offer-tier-icon-unmet{color:var(--color-primary-dark)}.s-conditional-offer-tier-status{position:absolute;bottom:-0.125rem;display:inline-flex;height:1rem;width:1rem;align-items:center;justify-content:center;border-radius:9999px;border-width:2px;--tw-border-opacity:1;border-color:rgb(255 255 255 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));inset-inline-start:-2px}.s-conditional-offer-tier-status-met{color:var(--color-primary-dark)}.s-conditional-offer-tier-status-unmet{--tw-text-opacity:1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.s-conditional-offer-tier-status-check{height:0.75rem;width:0.75rem;flex-shrink:0}.s-conditional-offer-tier-status-lock{height:0.625rem;width:0.625rem;flex-shrink:0}.s-conditional-offer-tier-body{display:flex;min-width:0px;flex-direction:column;gap:0.125rem}.s-conditional-offer-tier-title{font-size:0.75rem;line-height:1rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-tier-text{font-size:0.75rem;line-height:1rem;line-height:1.625;--tw-text-opacity:1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.s-conditional-offer-tier-text-met{text-decoration-line:line-through}.s-conditional-offer-skeleton-text{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:0.375rem}";
981
+ const sallaConditionalOfferCss = "/*!twlt*/:host{display:block}.s-conditional-offer-root{background-color:rgba(0, 137, 123, 0.06);background-color:color-mix(in srgb, var(--color-primary, #00897b) 7%, transparent)}.s-conditional-offer-badges::-webkit-scrollbar{height:3px}.s-conditional-offer-badges::-webkit-scrollbar-thumb{border-radius:9999px;background-color:rgba(0, 0, 0, 0.15)}.s-conditional-offer-badges::-webkit-scrollbar-track{background-color:transparent}.s-conditional-offer-tooltip{--s-conditional-offer-tooltip-shift:0px;--s-conditional-offer-tooltip-caret:0px;transform:translateX(calc(-50% + var(--s-conditional-offer-tooltip-shift)))}.s-conditional-offer-tooltip::before{content:\"\";position:absolute;bottom:100%;left:50%;transform:translateX(calc(-50% + var(--s-conditional-offer-tooltip-caret)));border:8px solid transparent;border-bottom-color:#fff}.s-conditional-offer-tier-icon-met,.s-conditional-offer-tier-icon-unmet{color:var(--color-primary-dark, #00695c);background-color:rgba(0, 137, 123, 0.12);background-color:color-mix(in srgb, var(--color-primary, #00897b) 14%, transparent)}.s-conditional-offer-tier:not(:last-child)::before{content:\"\";position:absolute;top:2.5rem;bottom:-1rem;inset-inline-start:1.125rem;width:1px;background-color:#d1d5db}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.w-6{width:1.5rem}.resize{resize:both}.gap-1{gap:0.25rem}.rounded{border-radius:var(--s-radius, 0.25rem)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.s-conditional-offer-root{margin-bottom:1.25rem;display:block;width:100%;min-width:0px;max-width:100%;border-radius:0.375rem;padding:0.75rem}@media (min-width: 768px){.s-conditional-offer-root{padding:1rem}}.s-conditional-offer-banner{display:flex;width:100%;min-width:0px;align-items:center;justify-content:space-between;gap:0.75rem}@media (min-width: 768px){.s-conditional-offer-banner{gap:1rem}}.s-conditional-offer-lead{display:flex;min-width:0px;flex:1 1 0%;align-items:center;gap:0.5rem}@media (min-width: 768px){.s-conditional-offer-lead{gap:0.75rem}}.s-conditional-offer-badges{display:flex;max-width:45%;flex-shrink:0;flex-wrap:nowrap;align-items:center;gap:0.25rem;overflow-x:auto;overscroll-behavior-x:contain;scroll-behavior:smooth;scrollbar-width:thin}.s-conditional-offer-badge{display:inline-flex;height:1.5rem;width:1.5rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;font-size:11px;line-height:1;--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.s-conditional-offer-badge-met{background-color:var(--color-primary)}.s-conditional-offer-badge-unmet{--tw-bg-opacity:1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.s-conditional-offer-badge-link{width:0.5rem;flex-shrink:0;border-top-width:1px}.s-conditional-offer-badge-link-met{border-color:var(--color-primary)}.s-conditional-offer-badge-link-unmet{border-style:dashed;--tw-border-opacity:1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.s-conditional-offer-badge-icon{height:0.75rem;width:0.75rem;flex-shrink:0}.s-conditional-offer-text{display:flex;min-width:0px;flex-direction:column;gap:0.125rem;text-align:start}.s-conditional-offer-heading{display:flex;min-width:0px;align-items:center;gap:0.375rem}.s-conditional-offer-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.75rem;line-height:1rem;--tw-text-opacity:1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.s-conditional-offer-info{position:relative;display:inline-flex;flex-shrink:0;align-items:center}.s-conditional-offer-info-trigger{display:inline-flex;height:1rem;width:1rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;--tw-text-opacity:1;color:rgb(115 115 115 / var(--tw-text-opacity, 1));transition-property:color, background-color, border-color, text-decoration-color, fill, stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms}.s-conditional-offer-info-trigger:hover{color:var(--color-primary-dark)}.s-conditional-offer-info-icon{height:0.875rem;width:0.875rem;flex-shrink:0}.s-conditional-offer-message{font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark)}@media (min-width: 768px){.s-conditional-offer-message{font-size:1rem;line-height:1.5rem}}.s-conditional-offer-toggle{align-items:center;justify-content:center;gap:0.375rem;font-size:0.875rem;line-height:1.25rem;--tw-text-opacity:1;color:rgb(38 38 38 / var(--tw-text-opacity, 1));transition-property:color, background-color, border-color, text-decoration-color, fill, stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms}.s-conditional-offer-toggle:hover{color:var(--color-primary-dark)}.s-conditional-offer-toggle-inline{display:none;flex-shrink:0}@media (min-width: 768px){.s-conditional-offer-toggle-inline{display:inline-flex}}.s-conditional-offer-toggle-bar{margin-top:0.5rem;display:inline-flex;width:100%;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));padding-left:0.75rem;padding-right:0.75rem;padding-top:0.625rem;padding-bottom:0.625rem}@media (min-width: 768px){.s-conditional-offer-toggle-bar{display:none}}.s-conditional-offer-toggle-icon{font-size:1rem;line-height:1.5rem;line-height:1;transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms}.s-conditional-offer-toggle-icon-open{--tw-rotate:180deg;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.s-conditional-offer-panel{margin-top:0.75rem;width:100%;min-width:0px;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));padding:0.75rem}.s-conditional-offer-gifts-header{margin-bottom:0.75rem;display:flex;align-items:center;gap:0.5rem}.s-conditional-offer-gifts-icon{display:inline-flex;height:2rem;width:2rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;border-width:1px;border-color:var(--color-primary);font-size:1rem;line-height:1.5rem;line-height:1;color:var(--color-primary)}.s-conditional-offer-gifts-title{font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-gifts-list{margin:0px;display:flex;list-style-type:none;flex-direction:column;gap:0.5rem;padding:0px}.s-conditional-offer-gift{display:flex;align-items:center;gap:0.75rem;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));padding:0.5rem}.s-conditional-offer-gift-thumb{display:inline-flex;height:3.5rem;width:3.5rem;flex-shrink:0;align-items:center;justify-content:center;overflow:hidden;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));font-size:1.25rem;line-height:1.75rem;--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.s-conditional-offer-gift-image{height:100%;width:100%;object-fit:contain}.s-conditional-offer-gift-body{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:0.125rem;text-align:start}.s-conditional-offer-gift-name{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;font-size:0.875rem;line-height:1.25rem;font-weight:700;--tw-text-opacity:1;color:rgb(38 38 38 / var(--tw-text-opacity, 1))}.s-conditional-offer-gift-prices{display:flex;align-items:center;gap:0.5rem;font-size:0.75rem;line-height:1rem}.s-conditional-offer-gift-free{font-weight:700;--tw-text-opacity:1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.s-conditional-offer-gift-price{--tw-text-opacity:1;color:rgb(115 115 115 / var(--tw-text-opacity, 1));text-decoration-line:line-through}.s-conditional-offer-gifts{margin-top:0.75rem;width:100%;min-width:0px;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1));padding:0.75rem}.s-conditional-offer-picker{display:flex;width:100%;min-width:0px;flex-direction:column;gap:0.75rem}.s-conditional-offer-picker-header{display:flex;align-items:center;gap:0.5rem}.s-conditional-offer-picker-counter{display:inline-flex;height:2.25rem;width:2.25rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;border-width:2px;border-color:var(--color-primary);font-size:0.75rem;line-height:1rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-picker-title{font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-picker-list{margin:0px;display:flex;list-style-type:none;gap:0.75rem;overflow-x:auto;overscroll-behavior-x:contain;scroll-behavior:smooth;padding:0px;padding-bottom:0.25rem;scrollbar-width:thin}.s-conditional-offer-picker-card{display:flex;width:9rem;flex-shrink:0;flex-direction:column;gap:0.375rem;text-align:start}.s-conditional-offer-picker-thumb{display:inline-flex;height:7rem;width:100%;align-items:center;justify-content:center;overflow:hidden;border-radius:0.375rem;border-width:1px;--tw-border-opacity:1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));font-size:1.5rem;line-height:2rem;--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.s-conditional-offer-picker-image{height:100%;width:100%;object-fit:contain}.s-conditional-offer-picker-name{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;font-size:0.875rem;line-height:1.25rem;--tw-text-opacity:1;color:rgb(38 38 38 / var(--tw-text-opacity, 1))}.s-conditional-offer-picker-prices{display:flex;align-items:center;gap:0.5rem;font-size:0.75rem;line-height:1rem}.s-conditional-offer-picker-price-regular{font-weight:700;--tw-text-opacity:1;color:rgb(23 23 23 / var(--tw-text-opacity, 1))}.s-conditional-offer-picker-actions{margin-top:auto;display:flex;width:100%;align-items:center;gap:0.375rem}.s-conditional-offer-picker-add{display:inline-flex;height:2.25rem;flex:1 1 0%;align-items:center;justify-content:center;border-radius:0.375rem;border-width:1px;border-color:var(--color-primary);font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark);transition-property:color, background-color, border-color, text-decoration-color, fill, stroke;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms}.s-conditional-offer-picker-add:hover{background-color:var(--color-primary);--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.s-conditional-offer-tooltip{position:absolute;top:calc(100% + 0.75rem);left:50%;z-index:10;width:min(22rem, calc(100vw - 2rem));border-radius:0.5rem;--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));padding:1rem;text-align:start;--tw-shadow:5px 10px 30px #2B2D340D;--tw-shadow-colored:5px 10px 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.s-conditional-offer-tooltip-title{font-size:0.875rem;line-height:1.25rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-tooltip-description{margin-top:0.25rem;font-size:0.75rem;line-height:1rem;line-height:1.625;--tw-text-opacity:1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.s-conditional-offer-tooltip-list{margin:0px;margin-top:0.75rem;display:flex;list-style-type:none;flex-direction:column;gap:1rem;padding:0px}.s-conditional-offer-tier{position:relative;display:flex;align-items:flex-start;gap:0.75rem}.s-conditional-offer-tier-icon{position:relative;display:inline-flex;height:2.25rem;width:2.25rem;flex-shrink:0;align-items:center;justify-content:center;border-radius:9999px;font-size:1rem;line-height:1.5rem;line-height:1}.s-conditional-offer-tier-icon-unmet{color:var(--color-primary-dark)}.s-conditional-offer-tier-status{position:absolute;bottom:-0.125rem;display:inline-flex;height:1rem;width:1rem;align-items:center;justify-content:center;border-radius:9999px;border-width:2px;--tw-border-opacity:1;border-color:rgb(255 255 255 / var(--tw-border-opacity, 1));--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));inset-inline-start:-2px}.s-conditional-offer-tier-status-met{color:var(--color-primary-dark)}.s-conditional-offer-tier-status-unmet{--tw-text-opacity:1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.s-conditional-offer-tier-status-check{height:0.75rem;width:0.75rem;flex-shrink:0}.s-conditional-offer-tier-status-lock{height:0.625rem;width:0.625rem;flex-shrink:0}.s-conditional-offer-tier-body{display:flex;min-width:0px;flex-direction:column;gap:0.125rem}.s-conditional-offer-tier-title{font-size:0.75rem;line-height:1rem;font-weight:700;color:var(--color-primary-dark)}.s-conditional-offer-tier-text{font-size:0.75rem;line-height:1rem;line-height:1.625;--tw-text-opacity:1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.s-conditional-offer-tier-text-met{text-decoration-line:line-through}.s-conditional-offer-skeleton-text{display:flex;min-width:0px;flex:1 1 0%;flex-direction:column;gap:0.375rem}";
923
982
 
924
983
  /** Root of every translation key this component uses. */
925
984
  const LANG = "blocks.cart_offer";
@@ -984,6 +1043,19 @@ const RELOCATION_ANCESTOR_LIMIT = 3;
984
1043
  * DOM with no cart in it. Past this deadline the full-width banner is the final answer.
985
1044
  */
986
1045
  const RELOCATION_WATCH_MS = 5000;
1046
+ /**
1047
+ * CP-2171 — how long the picker stays locked after an add succeeded when no fresh cart
1048
+ * lines arrive (a missed `cart::updated`). Long enough to cover the coalescing window and the
1049
+ * cart lookup; short enough that a lost event never leaves Add ignoring taps for long. Past it
1050
+ * the lock goes and the lines are read again — see `unlockOnStaleLines`.
1051
+ */
1052
+ const FREE_PRODUCT_UNLOCK_FALLBACK_MS = 5000;
1053
+ /** CP-2171 — how long the cart page fetch for new lines may take before falling back to a reload. */
1054
+ const CART_PAGE_FETCH_TIMEOUT_MS = 8000;
1055
+ /** `sessionStorage` key of the cart page reload guard (see `reloadForMissingCartLines`). */
1056
+ const CART_PAGE_RELOAD_KEY = 'salla-conditional-offer:reloaded-for';
1057
+ /** A theme's cart line forms. Product option forms (`.cart-options`) carry an `item-` id too, but are not lines. */
1058
+ const CART_LINE_FORMS = 'form[id^="item-"]:not(.cart-options form)';
987
1059
  /**
988
1060
  * Clear space kept between the ⓘ popover and the viewport edge when it is nudged back
989
1061
  * inside. Purely cosmetic — one gutter, both edges, both directions.
@@ -1016,10 +1088,32 @@ const EN_FALLBACKS = {
1016
1088
  details_title: 'Offer details',
1017
1089
  free_gift: 'Free Gift',
1018
1090
  free_products_title: 'Free products selected from the offer',
1091
+ // CP-5033 — the gift tier is reached but none of its gifts can be added.
1092
+ free_products_unavailable: 'The free products are currently unavailable',
1093
+ // CP-2171 — the free product picker.
1094
+ free_products_choose: 'Choose :count free products',
1095
+ // Its own key, not :count = 2: Arabic uses the dual form, with no number.
1096
+ free_products_choose_two: 'Choose 2 free products',
1097
+ free_product_choose_one: 'Choose your free product',
1098
+ // The banner line at the gift tier: "…Choose it/them now" while no gift is picked yet, then
1099
+ // the plain line once one is. Same one / two / :count split as the picker title.
1100
+ free_product_got_one: 'You got a free product',
1101
+ free_product_got_pick_one: 'You got a free product! Choose it now',
1102
+ free_products_got: 'You got :count free products',
1103
+ free_products_got_pick: 'You got :count free products! Choose them now',
1104
+ free_products_got_pick_two: 'You got 2 free products! Choose them now',
1105
+ free_products_got_two: 'You got 2 free products',
1106
+ // A picker tier's reward in "Add :remaining and get :reward" and the tooltip rows, instead of
1107
+ // the server's count-less "Free Gift". Same one / two / :count split.
1108
+ free_product_reward_one: 'a free product',
1109
+ free_products_reward: ':count free products',
1110
+ free_products_reward_two: '2 free products',
1019
1111
  guidance_conditions_unmet: 'Your cart doesn’t meet the conditions for the next reward yet',
1020
1112
  guidance_next: 'Add :remaining and get :reward',
1021
1113
  guidance_next_generic: 'Add :remaining to unlock the next reward',
1022
1114
  manage_offer: 'Manage offer',
1115
+ // The picker's toggle. Its own key, so offers without a picker keep master's "Manage offer".
1116
+ offer_toggle: 'Offer',
1023
1117
  tier_requirement: 'Spend :threshold and get :reward',
1024
1118
  tier_requirement_count: 'Buy :threshold and get :reward',
1025
1119
  tier_requirement_count_generic: 'Buy :threshold to unlock this offer',
@@ -1029,6 +1123,15 @@ const EN_FALLBACKS = {
1029
1123
  /** The gift price label reuses the platform's existing "free of charge" string. */
1030
1124
  const FREE_LABEL_KEY = 'pages.checkout.free_charge';
1031
1125
  const FREE_LABEL_FALLBACK = 'Free';
1126
+ /**
1127
+ * CP-2171 — the picker's add label is the platform's shared "Add", not a cart-offer key of its
1128
+ * own: the dictionaries keep one string per meaning.
1129
+ */
1130
+ const ADD_LABEL_KEY = 'common.elements.add';
1131
+ const ADD_LABEL_FALLBACK = 'Add';
1132
+ // The "Free products" panel title reuses the loyalty program's existing "Free products".
1133
+ const FREE_PRODUCTS_TITLE_KEY = 'pages.loyalty_program.free_product_title';
1134
+ const FREE_PRODUCTS_TITLE_FALLBACK = 'Free products';
1032
1135
  /**
1033
1136
  * Escape a merchant-controlled string before it is substituted into a translation
1034
1137
  * template that we then render with `innerHTML`.
@@ -1135,6 +1238,37 @@ const SallaConditionalOffer = class {
1135
1238
  this.canRender = true;
1136
1239
  /** Free-gift cart LINES belonging to this offer — the "ادارة العرض" card. */
1137
1240
  this.gifts = [];
1241
+ /** CP-2171 — every cart line (paid and free), for the picker's per-product state. */
1242
+ this.cartLines = [];
1243
+ /**
1244
+ * CP-2171 — whether `cartLines` came from a cart lookup that answered. Until then the
1245
+ * lines are `[]`: the "Free products" panel and the gift-pick banner line wait for it.
1246
+ */
1247
+ this.cartLinesLoaded = false;
1248
+ /** CP-2171 — the picker's product cards, keyed by ENCODED product id. */
1249
+ this.freeProductCards = {};
1250
+ /**
1251
+ * CP-2171 — the product (encoded id) an add is in flight for. Held until cart lines read
1252
+ * AFTER the add arrive, not just until the request ends: the cart refresh starts only after
1253
+ * the coalescing window, and a tap in between is ignored, so a double tap adds one unit.
1254
+ */
1255
+ this.pendingFreeProduct = null;
1256
+ /** `giftsGeneration` when the pending change succeeded; a cart load started after it unlocks. */
1257
+ this.pendingFreeProductSettledAt = null;
1258
+ this.pendingFreeProductTimer = null;
1259
+ /**
1260
+ * `giftsGeneration` when an add from the picker succeeded: the first cart lines read after it
1261
+ * are checked against the page (see `showMissingCartLines`). Kept apart from the
1262
+ * button lock on purpose: the lock's fallback can fire before those lines arrive, and the
1263
+ * page still has to show the new line when they do.
1264
+ */
1265
+ this.cartPageCheckAfter = null;
1266
+ /** A cart page fetch for missing lines is in flight; lines read meanwhile wait in `pendingCartPageLines`. */
1267
+ this.insertingCartLines = false;
1268
+ this.pendingCartPageLines = null;
1269
+ /** Re-reads of the cart after a failed lookup while a picker change waits; see `retryPickerCartLookup`. */
1270
+ this.pickerLookupRetries = 0;
1271
+ this.pickerLookupTimer = null;
1138
1272
  /**
1139
1273
  * How many LINES the cart holds — `null` while that is genuinely UNKNOWN.
1140
1274
  *
@@ -1400,6 +1534,9 @@ const SallaConditionalOffer = class {
1400
1534
  clearTimeout(this.cartRefreshTimer);
1401
1535
  this.cartRefreshTimer = null;
1402
1536
  }
1537
+ // A dead host has no picker to unlock; drop the fallback timer with the rest.
1538
+ this.unlockFreeProduct();
1539
+ this.clearPickerLookupRetry();
1403
1540
  // Likewise: the timer goes, `cartLookupRetryPending` stays.
1404
1541
  if (this.cartLookupRetryTimer !== null) {
1405
1542
  clearTimeout(this.cartLookupRetryTimer);
@@ -1889,6 +2026,8 @@ const SallaConditionalOffer = class {
1889
2026
  return;
1890
2027
  this.cartRefreshPending = false;
1891
2028
  this.refresh();
2029
+ // A new cart change starts a new picker retry episode: this pass asks again itself.
2030
+ this.clearPickerLookupRetry();
1892
2031
  refreshCartFreeGifts().then(() => this.loadCart(), () => this.loadCart());
1893
2032
  }
1894
2033
  /**
@@ -1955,6 +2094,14 @@ const SallaConditionalOffer = class {
1955
2094
  const text = salla.lang.get(FREE_LABEL_KEY);
1956
2095
  return text && text !== FREE_LABEL_KEY ? text : FREE_LABEL_FALLBACK;
1957
2096
  }
2097
+ getAddLabel() {
2098
+ return this.sharedLabel(ADD_LABEL_KEY, ADD_LABEL_FALLBACK);
2099
+ }
2100
+ /** A platform-wide label (not `blocks.cart_offer.*`): its own text, or `fallback` when missing. */
2101
+ sharedLabel(key, fallback) {
2102
+ const text = salla.lang.get(key);
2103
+ return text && text !== key ? text : fallback;
2104
+ }
1958
2105
  /**
1959
2106
  * Read a `blocks.cart_offer.*` translation.
1960
2107
  *
@@ -2084,6 +2231,7 @@ const SallaConditionalOffer = class {
2084
2231
  // Re-asked now that `this.offer` is known, because the gift filter is keyed to
2085
2232
  // the offer id — the early pass in `load()` could only answer the COUNT.
2086
2233
  this.loadCart();
2234
+ this.loadFreeProductCards();
2087
2235
  }
2088
2236
  catch (error) {
2089
2237
  if (this.isSuperseded(generation))
@@ -2148,8 +2296,8 @@ const SallaConditionalOffer = class {
2148
2296
  */
2149
2297
  loadCart() {
2150
2298
  const generation = ++this.giftsGeneration;
2151
- return Promise.all([getCartFreeGiftItems(), getCartItemCount()])
2152
- .then(([items, count]) => {
2299
+ return Promise.all([getCartFreeGiftItems(), getCartItemCount(), getCartProductLines()])
2300
+ .then(([items, count, lines]) => {
2153
2301
  if (generation !== this.giftsGeneration || this.isDisconnected)
2154
2302
  return;
2155
2303
  /**
@@ -2163,6 +2311,8 @@ const SallaConditionalOffer = class {
2163
2311
  */
2164
2312
  if (this.cartRefreshPending)
2165
2313
  return;
2314
+ // Was this pass the cart, or a failed lookup's fallback? See below.
2315
+ const answered = hasAuthoritativeCartData();
2166
2316
  /**
2167
2317
  * NEVER walks a KNOWN count back to `null`. `count` is `null` whenever the
2168
2318
  * shared lookup could not answer — which, on the removal path, is every
@@ -2199,6 +2349,28 @@ const SallaConditionalOffer = class {
2199
2349
  this.deferredByEmptyCart = false;
2200
2350
  this.refresh();
2201
2351
  }
2352
+ /**
2353
+ * A failed lookup hands back the cached lines, or `[]` right after a cart
2354
+ * update — not the cart. Publishing them would show an added gift as Add
2355
+ * again (one more tap is a PAID unit) and end the post-change checks below
2356
+ * as though fresh lines had arrived. Keep what we had, and while a picker
2357
+ * change is waiting on real lines — or a shown picker has never had any, so
2358
+ * Add is still disabled — ask again: the count alone schedules no retry once
2359
+ * the cart event has supplied one. A cart with no items (no banner) asks
2360
+ * nothing more.
2361
+ *
2362
+ * An offer without a picker has none of this at stake and goes on as it always
2363
+ * did: its gift card must still follow a removed gift, and nothing would retry.
2364
+ */
2365
+ const hasPicker = this.getPickerStage(this.getStages()) !== null;
2366
+ const awaitsPickerChange = this.pendingFreeProductSettledAt !== null || this.cartPageCheckAfter !== null;
2367
+ if (!answered && (hasPicker || awaitsPickerChange)) {
2368
+ if (awaitsPickerChange || (!this.cartLinesLoaded && this.hasCartItems())) {
2369
+ this.retryPickerCartLookup();
2370
+ }
2371
+ return;
2372
+ }
2373
+ this.clearPickerLookupRetry();
2202
2374
  /**
2203
2375
  * The gift card is keyed to the offer, so it can only be filled once the
2204
2376
  * offer is known. The early pass fired from `load()` runs BEFORE
@@ -2208,6 +2380,16 @@ const SallaConditionalOffer = class {
2208
2380
  * one.
2209
2381
  */
2210
2382
  this.gifts = this.offer ? this.selectOwnGifts(items) : [];
2383
+ this.cartLines = this.offer ? lines : [];
2384
+ this.cartLinesLoaded = !!this.offer;
2385
+ // These lines were read after the pending change landed: the picker may act again.
2386
+ if (this.pendingFreeProductSettledAt !== null && generation > this.pendingFreeProductSettledAt) {
2387
+ this.unlockFreeProduct();
2388
+ }
2389
+ if (this.cartPageCheckAfter !== null && generation > this.cartPageCheckAfter) {
2390
+ this.cartPageCheckAfter = null;
2391
+ this.showMissingCartLines(lines);
2392
+ }
2211
2393
  })
2212
2394
  .catch(error => {
2213
2395
  this.safeLog('cart lookup failed', error);
@@ -2239,6 +2421,27 @@ const SallaConditionalOffer = class {
2239
2421
  this.loadCart();
2240
2422
  }, CART_LOOKUP_RETRY_DELAYS_MS[this.cartLookupRetries]);
2241
2423
  }
2424
+ /**
2425
+ * Ask for the cart again after a lookup that could not answer while a picker change is
2426
+ * waiting on real lines. Its own bounded episode, on the count retry's delays: that one
2427
+ * ends as soon as the cart event has supplied a count, which is exactly when this is needed.
2428
+ */
2429
+ retryPickerCartLookup() {
2430
+ if (this.pickerLookupTimer !== null || this.pickerLookupRetries >= CART_LOOKUP_RETRY_DELAYS_MS.length)
2431
+ return;
2432
+ this.pickerLookupTimer = setTimeout(() => {
2433
+ this.pickerLookupTimer = null;
2434
+ this.pickerLookupRetries++;
2435
+ this.loadCart();
2436
+ }, CART_LOOKUP_RETRY_DELAYS_MS[this.pickerLookupRetries]);
2437
+ }
2438
+ clearPickerLookupRetry() {
2439
+ this.pickerLookupRetries = 0;
2440
+ if (this.pickerLookupTimer === null)
2441
+ return;
2442
+ clearTimeout(this.pickerLookupTimer);
2443
+ this.pickerLookupTimer = null;
2444
+ }
2242
2445
  /**
2243
2446
  * End the retry episode: a lookup answered, or the cart changed under it. The count
2244
2447
  * goes back to zero so a LATER failure gets its own full set of attempts.
@@ -2295,7 +2498,7 @@ const SallaConditionalOffer = class {
2295
2498
  * an older backend — is measured by the offer-level value exactly as before.
2296
2499
  */
2297
2500
  getProgress(stages) {
2298
- return computeOfferStageProgress(this.offer?.details?.current_value, stages.map(({ min_spend }) => min_spend), this.offer?.details?.start_value, stages.map(({ is_applied }) => is_applied), stages.map(({ current_value }) => current_value));
2501
+ return computeOfferStageProgress(this.offer?.details?.current_value, stages.map(({ min_spend }) => min_spend), this.offer?.details?.start_value, stages.map(({ is_applied }) => is_applied), stages.map(({ current_value }) => current_value), stages.map(({ can_add_free }) => can_add_free));
2299
2502
  }
2300
2503
  /**
2301
2504
  * A tier's render key. `id` is the stable identity across refetches; the array
@@ -2352,6 +2555,15 @@ const SallaConditionalOffer = class {
2352
2555
  * why every caller has a reward-less sentence to fall back to.
2353
2556
  */
2354
2557
  getRewardText(discount) {
2558
+ // CP-2171 — Figma: "Add 140 and get 2 free products". The server's label has no count.
2559
+ if (this.isPickerTier(discount)) {
2560
+ const limit = Math.max(1, Math.floor(Number(discount.max_free_products) || 1));
2561
+ if (limit === 1)
2562
+ return this.trans('free_product_reward_one');
2563
+ if (limit === 2)
2564
+ return this.trans('free_products_reward_two');
2565
+ return this.trans('free_products_reward', { count: `${limit}` });
2566
+ }
2355
2567
  return (discount?.reward_label ?? '').trim();
2356
2568
  }
2357
2569
  /**
@@ -2373,7 +2585,13 @@ const SallaConditionalOffer = class {
2373
2585
  getGuidanceMessage(stages, progress) {
2374
2586
  if (progress.nextUnmetIndex === NO_STAGE_REACHED)
2375
2587
  return '';
2376
- const remainingValue = getRemainingToStage(progress.nextUnmetIndex, progress, this.stageMeasure());
2588
+ // CP-2171 — the engine's own gap for a picker tier, when the server sends it: `current_value`
2589
+ // counts the gift units the tier would free, so it can read as past the threshold while the
2590
+ // tier is still short ("conditions unmet" with no amount).
2591
+ const engineAmountNeeded = Number(stages[progress.nextUnmetIndex]?.amount_needed);
2592
+ const remainingValue = this.isPickerTier(stages[progress.nextUnmetIndex]) && Number.isFinite(engineAmountNeeded) && engineAmountNeeded > 0
2593
+ ? engineAmountNeeded
2594
+ : getRemainingToStage(progress.nextUnmetIndex, progress, this.stageMeasure());
2377
2595
  if (remainingValue <= 0)
2378
2596
  return this.trans('guidance_conditions_unmet');
2379
2597
  const remaining = this.formatRequirement(remainingValue);
@@ -2651,26 +2869,403 @@ const SallaConditionalOffer = class {
2651
2869
  }, innerHTML: this.getTierSentence(discount, index$1, progress) }))));
2652
2870
  }))));
2653
2871
  }
2872
+ /**
2873
+ * CP-2171 — the tier the free product picker belongs to: the first `free_product` tier that
2874
+ * carries a non-empty `free_products` list. `null` on an offer without one, which keeps the
2875
+ * read-only gift card exactly as before. An empty list (every listed product unavailable) is
2876
+ * treated as no picker: there is nothing to choose, so a "Choose N" header would be a dead end.
2877
+ */
2878
+ getPickerStage(stages) {
2879
+ return stages.find(stage => this.isPickerTier(stage)) ?? null;
2880
+ }
2881
+ isPickerTier(stage) {
2882
+ return stage?.type === DiscountType.FREE_PRODUCT && Array.isArray(stage.free_products) && stage.free_products.length > 0;
2883
+ }
2884
+ /**
2885
+ * CP-5033 — the furthest tier the cart's value reaches is a gift tier with none of its gifts
2886
+ * addable (the server lists only what can be added: out of stock, hidden and deleted are left
2887
+ * out), and the cart holds none of this offer's gifts. The engine pays nothing for that tier,
2888
+ * so neither "congrats" nor "conditions unmet" is true.
2889
+ */
2890
+ areGiftsUnavailable(stages, progress) {
2891
+ const stage = stages[progress.lastReachedIndex];
2892
+ return stage?.type === DiscountType.FREE_PRODUCT
2893
+ && Array.isArray(stage.free_products) && stage.free_products.length === 0
2894
+ && this.gifts.length === 0;
2895
+ }
2896
+ /**
2897
+ * Load the picker's product cards (name, image, price) — one `salla.product.fetch` for
2898
+ * the ids not loaded yet. Never rejects: a failed lookup leaves the cards out, not the banner.
2899
+ */
2900
+ loadFreeProductCards() {
2901
+ const stage = this.getPickerStage(this.getStages());
2902
+ const ids = (stage?.free_products ?? [])
2903
+ .map(ref => String(ref.id))
2904
+ .filter(id => id && !this.freeProductCards[id]);
2905
+ if (!ids.length)
2906
+ return Promise.resolve();
2907
+ const listedIds = new Set((stage.free_products ?? []).map(ref => String(ref.id)));
2908
+ return Promise.resolve()
2909
+ .then(() => salla.product.fetch({ source: 'selected', source_value: ids }))
2910
+ .then((response) => {
2911
+ if (this.isDisconnected)
2912
+ return;
2913
+ const cards = { ...this.freeProductCards };
2914
+ (Array.isArray(response?.data) ? response.data : []).forEach((product) => {
2915
+ const id = String(product?.id ?? '');
2916
+ if (!id || !listedIds.has(id))
2917
+ return;
2918
+ cards[id] = {
2919
+ id,
2920
+ name: String(product.name ?? ''),
2921
+ imageUrl: String(product.image?.url ?? ''),
2922
+ imageAlt: String(product.image?.alt ?? product.name ?? ''),
2923
+ price: Number.isFinite(Number(product.price)) ? Number(product.price) : null,
2924
+ };
2925
+ });
2926
+ this.freeProductCards = cards;
2927
+ })
2928
+ .catch(error => this.safeLog('free product lookup failed', error));
2929
+ }
2930
+ /**
2931
+ * The picker's view of the cart for this offer.
2932
+ *
2933
+ * - `freeCount`: products a line of THIS offer made free — the engine's own verdict, read
2934
+ * off the cart lines (`added_by_offer` + `offer_id`), never re-derived here. Gifts of other
2935
+ * offers (another cart offer, Buy X Get Y) are not counted against this offer's limit.
2936
+ * - `reached`: the tier's threshold is met. Read from `current_value` against `min_spend`,
2937
+ * NOT from `is_applied`: a free product tier is only "applied" once a listed product is
2938
+ * in the cart, so gating "Add" on it would never let the customer add the first one.
2939
+ */
2940
+ getPickerState(stage) {
2941
+ const allFreeLines = this.cartLines.filter(line => line.freeQuantity > 0);
2942
+ const freeLines = this.selectOwnFreeLines(allFreeLines);
2943
+ const free = new Set(freeLines.map(line => line.productId).filter(id => id !== null));
2944
+ const limit = Math.max(1, Math.floor(Number(stage.max_free_products) || 1));
2945
+ // The engine's answer when the server sends it; `current_value >= min_spend` is only a
2946
+ // fallback — it is gross of earlier discounts, so it can say "choose" while the engine
2947
+ // would still charge for the product.
2948
+ const value = Number(stage.current_value ?? this.offer?.details?.current_value ?? 0);
2949
+ const reached = typeof stage.can_add_free === 'boolean'
2950
+ ? stage.can_add_free
2951
+ : Number.isFinite(value) && value >= Number(stage.min_spend);
2952
+ return { limit, reached, freeCount: free.size };
2953
+ }
2954
+ /**
2955
+ * The free lines this offer granted — the SAME rule as `selectOwnGifts`, so the picker and
2956
+ * the gifts card never disagree: lines matching the offer id; only when none match, the
2957
+ * lines whose `offer_id` is unreadable.
2958
+ */
2959
+ selectOwnFreeLines(lines) {
2960
+ const offerId = Number(this.offer?.id);
2961
+ if (!Number.isFinite(offerId))
2962
+ return lines;
2963
+ const owned = lines.filter(line => line.offerId !== null && Number(line.offerId) === offerId);
2964
+ return owned.length ? owned : lines.filter(line => line.offerId === null);
2965
+ }
2966
+ /**
2967
+ * Add a free product to the cart — always from the cart page, never a redirect.
2968
+ *
2969
+ * Quick-add, exactly like the single free product was auto-added before CP-2171: a variant
2970
+ * (SKU) product gets its default SKU — `is_default` first, or the first in stock when stock
2971
+ * is per SKU (`AddToCart::addItemToCart()`) — with that SKU's option values. Any listed
2972
+ * product in the cart counts, so the engine then frees it. A failed add is left to the SDK,
2973
+ * which already shows the shopper its error notice.
2974
+ */
2975
+ addFreeProduct(card) {
2976
+ if (this.pendingFreeProduct)
2977
+ return;
2978
+ this.pendingFreeProduct = card.id;
2979
+ this.runFreeProductChange(() => Promise.resolve(salla.cart.addItem(card.id)).then(() => { this.cartPageCheckAfter = this.giftsGeneration; }), 'free product add failed');
2980
+ }
2981
+ /** Its own method so specs can observe it; the mock window cannot reload. */
2982
+ reloadPage() {
2983
+ window.location.reload();
2984
+ }
2985
+ /**
2986
+ * Its own method so specs can stub it; the mock window has no `fetch`. Aborted after
2987
+ * `CART_PAGE_FETCH_TIMEOUT_MS`: a fetch that never settles would hold `insertingCartLines`
2988
+ * for the page view, and every later line would only be queued behind it.
2989
+ */
2990
+ fetchCartPageHtml() {
2991
+ const controller = new AbortController();
2992
+ const timer = setTimeout(() => controller.abort(), CART_PAGE_FETCH_TIMEOUT_MS);
2993
+ // `no-store`: a cached copy of the page would lack the new line and force the reload.
2994
+ return fetch(window.location.href, { cache: 'no-store', credentials: 'same-origin', headers: { Accept: 'text/html' }, signal: controller.signal })
2995
+ .then(response => (response.ok ? response.text() : Promise.reject(new Error(`cart page ${response.status}`))))
2996
+ .finally(() => clearTimeout(timer));
2997
+ }
2998
+ /**
2999
+ * Show the cart lines an add from the picker created that the page has no `form#item-<id>`
3000
+ * for. Themes render cart lines on the server and only refresh lines already on the page, so
3001
+ * the new line (and its price) would otherwise appear only after a manual refresh.
3002
+ *
3003
+ * The cart page is fetched again and the missing line forms are inserted, in the server's
3004
+ * order — no reload. The fetched markup is fresh, so its prices need no update. Its inline
3005
+ * handlers and web components work as on a loaded page; only what the theme's own script wires
3006
+ * to a line at page load (e.g. raed's loading overlay on a quantity change) is missing.
3007
+ * Any failure (fetch, a line the fetched page lacks) falls back to `reloadForMissingCartLines`.
3008
+ *
3009
+ * Left alone on a page with no cart line forms at all (not a cart page this can reason about).
3010
+ * One fetch at a time; lines read meanwhile are checked again after it.
3011
+ */
3012
+ showMissingCartLines(lines) {
3013
+ if (this.insertingCartLines) {
3014
+ this.pendingCartPageLines = lines;
3015
+ return;
3016
+ }
3017
+ const missing = this.missingCartLineIds(lines);
3018
+ if (!missing.length)
3019
+ return;
3020
+ this.insertingCartLines = true;
3021
+ // Started inside the chain, so a synchronous throw (no `fetch`) still reaches `catch` and `finally`.
3022
+ Promise.resolve()
3023
+ .then(() => this.fetchCartPageHtml())
3024
+ .then(html => {
3025
+ if (this.isDisconnected || this.cartChangedDuringPageFetch())
3026
+ return;
3027
+ const stillMissing = this.missingCartLineIds(this.cartLines);
3028
+ if (stillMissing.length && !this.insertCartLineForms(html, stillMissing)) {
3029
+ this.reloadForMissingCartLines(stillMissing);
3030
+ }
3031
+ })
3032
+ .catch(error => {
3033
+ this.safeLog('cart page fetch for the new lines failed, reloading', error);
3034
+ if (this.isDisconnected || this.cartChangedDuringPageFetch())
3035
+ return;
3036
+ this.reloadForMissingCartLines(this.missingCartLineIds(this.cartLines));
3037
+ })
3038
+ .finally(() => {
3039
+ this.insertingCartLines = false;
3040
+ const pending = this.pendingCartPageLines;
3041
+ this.pendingCartPageLines = null;
3042
+ if (pending && !this.isDisconnected)
3043
+ this.showMissingCartLines(pending);
3044
+ });
3045
+ }
3046
+ /**
3047
+ * A picker change started while the cart page was fetched (e.g. a second add),
3048
+ * so neither the fetched page nor the cart lines can be trusted: inserting now could bring
3049
+ * back a line the cart no longer has. The answer is dropped and the cart read after that
3050
+ * change checks the page again (the change itself arms `cartPageCheckAfter` when it lands;
3051
+ * armed here too, for a change that fails and so arms nothing).
3052
+ *
3053
+ * Lines are always read from `cartLines` once the page arrives, never from the call's own
3054
+ * `lines`: a change that landed during the fetch has already replaced them there.
3055
+ */
3056
+ cartChangedDuringPageFetch() {
3057
+ // Lines not read since a change (the lock timed out on them) are no basis either.
3058
+ if (this.pendingFreeProduct === null && this.cartLinesLoaded)
3059
+ return false;
3060
+ if (this.cartPageCheckAfter === null)
3061
+ this.cartPageCheckAfter = this.giftsGeneration;
3062
+ return true;
3063
+ }
3064
+ /** Ids of `lines` the page has no form for, sorted; empty on a page with no cart line forms. */
3065
+ missingCartLineIds(lines) {
3066
+ try {
3067
+ if (!document.querySelector(CART_LINE_FORMS))
3068
+ return [];
3069
+ return lines.map(line => line.id).filter(id => !document.getElementById(`item-${id}`)).sort();
3070
+ }
3071
+ catch (error) {
3072
+ this.safeLog('cart page check after a free product add failed', error);
3073
+ return [];
3074
+ }
3075
+ }
3076
+ /**
3077
+ * Insert the `missing` line forms from the fetched cart page `html`, each right before the next
3078
+ * line the page already has (in the fetched order), else after the last one. Returns false —
3079
+ * nothing inserted — when the fetched page lacks any of them.
3080
+ */
3081
+ insertCartLineForms(html, missing) {
3082
+ try {
3083
+ const fetched = Array.from(new DOMParser().parseFromString(html, 'text/html').querySelectorAll(CART_LINE_FORMS));
3084
+ // `getAttribute`, not `form.id`: a line form holds an `<input name="id">`, which shadows the property.
3085
+ const fetchedIds = fetched.map(form => form.getAttribute('id'));
3086
+ if (missing.some(id => !fetchedIds.includes(`item-${id}`)))
3087
+ return false;
3088
+ fetched.forEach((form, index) => {
3089
+ const id = fetchedIds[index];
3090
+ if (!missing.includes(id.slice('item-'.length)) || document.getElementById(id))
3091
+ return;
3092
+ // Moved, not cloned: inserting adopts it into this document, and its web components
3093
+ // upgrade once connected. Its inline handlers (`onchange`, delete `onclick`) come along.
3094
+ const next = fetchedIds.slice(index + 1).map(nextId => document.getElementById(nextId)).find(Boolean);
3095
+ if (next) {
3096
+ next.parentNode.insertBefore(form, next);
3097
+ }
3098
+ else {
3099
+ const onPage = document.querySelectorAll(CART_LINE_FORMS);
3100
+ const last = onPage[onPage.length - 1];
3101
+ last.parentNode.insertBefore(form, last.nextSibling);
3102
+ }
3103
+ });
3104
+ return missing.every(id => document.getElementById(`item-${id}`));
3105
+ }
3106
+ catch (error) {
3107
+ this.safeLog('cart line insert failed', error);
3108
+ return false;
3109
+ }
3110
+ }
3111
+ /**
3112
+ * Reload once, when the missing lines could not be inserted. Never twice for the same missing
3113
+ * lines, so a line a theme never renders cannot loop the page. The guard lives in
3114
+ * `sessionStorage`; where storage is blocked the page still reloads, but never twice in a row.
3115
+ */
3116
+ reloadForMissingCartLines(missing) {
3117
+ if (!missing.length)
3118
+ return;
3119
+ const signature = missing.join(',');
3120
+ const stored = this.readReloadGuard();
3121
+ if (stored === signature)
3122
+ return;
3123
+ if (stored === undefined && this.pageWasReloaded())
3124
+ return;
3125
+ this.writeReloadGuard(signature);
3126
+ this.reloadPage();
3127
+ }
3128
+ /** The reload guard, or `undefined` when storage cannot be read (blocked, disabled). */
3129
+ readReloadGuard() {
3130
+ try {
3131
+ return window.sessionStorage.getItem(CART_PAGE_RELOAD_KEY);
3132
+ }
3133
+ catch {
3134
+ return undefined;
3135
+ }
3136
+ }
3137
+ writeReloadGuard(signature) {
3138
+ try {
3139
+ window.sessionStorage.setItem(CART_PAGE_RELOAD_KEY, signature);
3140
+ }
3141
+ catch {
3142
+ // Storage full or blocked: pageWasReloaded() is the guard on the next load.
3143
+ }
3144
+ }
3145
+ /** Whether this page load is itself a reload — the guard when storage is unavailable. */
3146
+ pageWasReloaded() {
3147
+ try {
3148
+ const navigation = window.performance?.getEntriesByType?.('navigation')?.[0];
3149
+ return navigation?.type === 'reload';
3150
+ }
3151
+ catch {
3152
+ return false;
3153
+ }
3154
+ }
3155
+ /**
3156
+ * Run one add. A failure unlocks at once — nothing changed. A success stays locked
3157
+ * until a cart load that started after it publishes (see `loadCart`), or the fallback fires.
3158
+ */
3159
+ runFreeProductChange(change, failure) {
3160
+ Promise.resolve()
3161
+ .then(change)
3162
+ .then(() => {
3163
+ if (this.isDisconnected)
3164
+ return this.unlockFreeProduct();
3165
+ this.pendingFreeProductSettledAt = this.giftsGeneration;
3166
+ this.pendingFreeProductTimer = setTimeout(() => this.unlockOnStaleLines(), FREE_PRODUCT_UNLOCK_FALLBACK_MS);
3167
+ })
3168
+ .catch(error => {
3169
+ this.safeLog(failure, error);
3170
+ this.unlockFreeProduct();
3171
+ // A failed change fires no cart event, so nothing else checks a cart page answer
3172
+ // that was dropped while this change was in flight.
3173
+ if (this.cartPageCheckAfter !== null)
3174
+ this.loadCart();
3175
+ });
3176
+ }
3177
+ /**
3178
+ * The lock's fallback: no cart lines read after the change have arrived, so the lines on
3179
+ * screen are the ones from BEFORE it. Release the lock, mark the lines unread and ask again
3180
+ * on a fresh read — `loadCart` retries within its bound while the picker has no lines.
3181
+ */
3182
+ unlockOnStaleLines() {
3183
+ this.unlockFreeProduct();
3184
+ this.cartLinesLoaded = false;
3185
+ this.clearPickerLookupRetry();
3186
+ refreshCartFreeGifts().then(() => this.loadCart(), () => this.loadCart());
3187
+ }
3188
+ unlockFreeProduct() {
3189
+ if (this.pendingFreeProductTimer !== null)
3190
+ clearTimeout(this.pendingFreeProductTimer);
3191
+ this.pendingFreeProductTimer = null;
3192
+ this.pendingFreeProductSettledAt = null;
3193
+ this.pendingFreeProduct = null;
3194
+ }
3195
+ /**
3196
+ * CP-2171 — Figma's banner line at the gift tier: "You got N free products", with
3197
+ * "Choose them now" while none is picked yet. N is the tier's limit, not the count picked.
3198
+ */
3199
+ pickerGotMessage(limit, awaitsPick) {
3200
+ if (limit === 1)
3201
+ return this.trans(awaitsPick ? 'free_product_got_pick_one' : 'free_product_got_one');
3202
+ if (limit === 2)
3203
+ return this.trans(awaitsPick ? 'free_products_got_pick_two' : 'free_products_got_two');
3204
+ return this.trans(awaitsPick ? 'free_products_got_pick' : 'free_products_got', { count: `${limit}` });
3205
+ }
3206
+ getPickerCards(stage) {
3207
+ return (stage.free_products ?? [])
3208
+ .map(ref => this.freeProductCards[String(ref.id)])
3209
+ .filter(Boolean);
3210
+ }
3211
+ /** CP-2171 — Figma "Choose N free products": the counter, then one card per listed product. */
3212
+ renderPicker(stage) {
3213
+ const state = this.getPickerState(stage);
3214
+ const cards = this.getPickerCards(stage);
3215
+ // Cards still loading, or the lookup failed: no header and counter over an empty list.
3216
+ if (!cards.length)
3217
+ return null;
3218
+ return (index.h("div", { class: "s-conditional-offer-picker", "data-picker-reached": state.reached ? 'true' : 'false' }, index.h("div", { class: "s-conditional-offer-picker-header" }, index.h("span", { class: "s-conditional-offer-picker-counter", "aria-label": `${state.freeCount}/${state.limit}` }, `${state.freeCount}/${state.limit}`), index.h("span", { class: "s-conditional-offer-picker-title" }, state.limit === 1
3219
+ ? this.trans('free_product_choose_one')
3220
+ : state.limit === 2
3221
+ ? this.trans('free_products_choose_two')
3222
+ : this.trans('free_products_choose', { count: `${state.limit}` }))), index.h("ul", { class: "s-conditional-offer-picker-list" }, cards.map(card => this.renderPickerCard(card)))));
3223
+ }
3224
+ /**
3225
+ * One picker card: the product, its normal price and Add — the same on every card, whether
3226
+ * the product is in the cart, free or not. Add is never disabled: every tap adds one unit at
3227
+ * the normal price (also once the product is in the cart, and after the free limit is used
3228
+ * up) and the engine decides what is free. What IS free shows in the "Free products" panel
3229
+ * below, never on the card. A tap while the previous add is still in flight is ignored
3230
+ * (`addFreeProduct`), so one double tap is one unit.
3231
+ *
3232
+ * No remove: the picker never deletes a cart line. A free line can also hold paid units, and
3233
+ * deleting it would take what the customer bought with the gift — products leave the cart
3234
+ * from their own cart line only.
3235
+ */
3236
+ renderPickerCard(card) {
3237
+ return (index.h("li", { class: "s-conditional-offer-picker-card", "data-product-id": card.id }, index.h("span", { class: "s-conditional-offer-picker-thumb" }, card.imageUrl ? (index.h("img", { class: "s-conditional-offer-picker-image", src: card.imageUrl, alt: card.imageAlt || card.name, loading: "lazy", decoding: "async" })) : (index.h("i", { class: "sicon-gift", "aria-hidden": "true" }))), index.h("span", { class: "s-conditional-offer-picker-name" }, card.name), card.price !== null ? (index.h("span", { class: "s-conditional-offer-picker-prices" }, index.h("span", { class: "s-conditional-offer-picker-price-regular", innerHTML: salla.money(card.price) }))) : null, index.h("span", { class: "s-conditional-offer-picker-actions" }, index.h("button", { type: "button", class: "s-conditional-offer-picker-add", onClick: () => this.addFreeProduct(card) }, this.getAddLabel()))));
3238
+ }
2654
3239
  /**
2655
3240
  * "ادارة العرض". Rendered TWICE with complementary responsive visibility rather than
2656
3241
  * moved with `order:`: desktop wants it inline with the banner row, mobile wants a
2657
3242
  * full-width bar BELOW the gifts card, and those are different places
2658
3243
  * in the flow, not a different order within one flex line.
2659
3244
  */
2660
- renderGiftsToggle(variant) {
3245
+ renderGiftsToggle(variant, hasPicker) {
2661
3246
  return (index.h("button", { type: "button", id: this.elementId(`toggle-${variant}`), class: {
2662
3247
  's-conditional-offer-toggle': true,
2663
3248
  's-conditional-offer-toggle-inline': variant === 'inline',
2664
3249
  's-conditional-offer-toggle-bar': variant === 'bar',
2665
- }, "aria-expanded": this.isGiftsOpen ? 'true' : 'false', "aria-controls": this.elementId('gifts'), onClick: () => (this.isGiftsOpen = !this.isGiftsOpen) }, index.h("span", null, this.trans('manage_offer')), index.h("i", { class: {
3250
+ }, "aria-expanded": this.isGiftsOpen ? 'true' : 'false', "aria-controls": this.elementId('gifts'), onClick: () => (this.isGiftsOpen = !this.isGiftsOpen) }, index.h("span", null, this.trans(hasPicker ? 'offer_toggle' : 'manage_offer')), index.h("i", { class: {
2666
3251
  'sicon-keyboard_arrow_down': true,
2667
3252
  's-conditional-offer-toggle-icon': true,
2668
3253
  's-conditional-offer-toggle-icon-open': this.isGiftsOpen,
2669
3254
  }, "aria-hidden": "true" })));
2670
3255
  }
2671
- /** Spec §5 — the gifts card: one read-only row per gift line. */
2672
- renderGiftsCard() {
2673
- return (index.h("div", { class: "s-conditional-offer-panel", id: this.elementId('gifts') }, index.h("div", { class: "s-conditional-offer-gifts-header" }, index.h("span", { class: "s-conditional-offer-gifts-icon" }, index.h("i", { class: "sicon-gift", "aria-hidden": "true" })), index.h("span", { class: "s-conditional-offer-gifts-title" }, this.trans('free_products_title'))), index.h("ul", { class: "s-conditional-offer-gifts-list" }, this.gifts.map(gift => this.renderGiftRow(gift)))));
3256
+ /**
3257
+ * Spec §5 — the gifts card: one row per gift line. Read-only on an offer without a picker,
3258
+ * exactly as before; with the CP-2171 picker it becomes the Figma "Free products" panel,
3259
+ * shown only once a gift is in the cart. Its rows are read-only, as are the picker cards: a
3260
+ * gift leaves the cart from its own cart line.
3261
+ */
3262
+ renderGiftsCard(pickerStage = null) {
3263
+ // No picker: the card exactly as before CP-2171 — no wrapper, so no new box styling.
3264
+ if (!pickerStage) {
3265
+ return (index.h("div", { class: "s-conditional-offer-panel", id: this.elementId('gifts') }, index.h("div", { class: "s-conditional-offer-gifts-header" }, index.h("span", { class: "s-conditional-offer-gifts-icon" }, index.h("i", { class: "sicon-gift", "aria-hidden": "true" })), index.h("span", { class: "s-conditional-offer-gifts-title" }, this.trans('free_products_title'))), index.h("ul", { class: "s-conditional-offer-gifts-list" }, this.gifts.map(gift => this.renderGiftRow(gift)))));
3266
+ }
3267
+ const state = this.getPickerState(pickerStage);
3268
+ return (index.h("div", { class: "s-conditional-offer-panel", id: this.elementId('gifts') }, state.reached ? this.renderPicker(pickerStage) : null, this.gifts.length ? (index.h("div", { class: "s-conditional-offer-gifts" }, index.h("div", { class: "s-conditional-offer-gifts-header" }, index.h("span", { class: "s-conditional-offer-gifts-icon" }, index.h("i", { class: "sicon-gift", "aria-hidden": "true" })), index.h("span", { class: "s-conditional-offer-gifts-title" }, this.sharedLabel(FREE_PRODUCTS_TITLE_KEY, FREE_PRODUCTS_TITLE_FALLBACK))), index.h("ul", { class: "s-conditional-offer-gifts-list" }, this.gifts.map(gift => this.renderGiftRow(gift))))) : null));
2674
3269
  }
2675
3270
  renderGiftRow(gift) {
2676
3271
  return (index.h("li", { class: "s-conditional-offer-gift", "data-item-id": gift.id }, index.h("span", { class: "s-conditional-offer-gift-thumb" }, gift.imageUrl ? (index.h("img", { class: "s-conditional-offer-gift-image", src: gift.imageUrl, alt: gift.imageAlt || gift.name, loading: "lazy", decoding: "async" })) : (index.h("i", { class: "sicon-gift", "aria-hidden": "true" }))), index.h("div", { class: "s-conditional-offer-gift-body" }, index.h("span", { class: "s-conditional-offer-gift-name" }, gift.name || this.trans('free_gift')), index.h("span", { class: "s-conditional-offer-gift-prices" }, index.h("span", { class: "s-conditional-offer-gift-free" }, this.getFreeLabel()), gift.originalPrice !== null ? (
@@ -2717,12 +3312,26 @@ const SallaConditionalOffer = class {
2717
3312
  const progress = this.getProgress(stages);
2718
3313
  const guidance = this.getGuidanceMessage(stages, progress);
2719
3314
  const detailsTitle = this.trans('details_title');
2720
- // Spec §2 — the accordion exists only when there is something to manage.
2721
- const hasGifts = this.gifts.length > 0;
2722
- return (index.h(index.Host, { class: "s-conditional-offer-root" }, index.h("div", { class: "s-conditional-offer-banner" }, index.h("div", { class: "s-conditional-offer-lead" }, this.renderBadges(stages, progress), index.h("div", { class: "s-conditional-offer-text" }, index.h("div", { class: "s-conditional-offer-heading", onMouseEnter: () => (this.isTooltipHovered = true), onMouseLeave: () => (this.isTooltipHovered = false) }, index.h("span", { class: "s-conditional-offer-name" }, this.offer.title), index.h("span", { class: "s-conditional-offer-info", ref: (el) => (this.infoAnchorEl = el) }, index.h("button", { type: "button", class: "s-conditional-offer-info-trigger", ref: (el) => (this.infoTriggerEl = el), "aria-label": detailsTitle, title: detailsTitle, "aria-expanded": this.isTooltipOpen ? 'true' : 'false', "aria-describedby": this.elementId('tooltip'), onClick: () => this.toggleTooltip() }, index.h(InformationIcon, { class: "s-conditional-offer-info-icon" })), this.isTooltipOpen ? this.renderTooltip(stages, progress) : null)), progress.allMet ? (index.h("div", { class: "s-conditional-offer-message", role: "status" }, this.trans('congrats'))) : guidance ? (
3315
+ // Spec §2 — the accordion exists only when there is something to manage: gift lines,
3316
+ // or (CP-2171) a free product picker — shown only once the customer has reached its
3317
+ // stage, then before anything is picked too.
3318
+ const pickerStage = this.getPickerStage(stages);
3319
+ const pickerState = pickerStage !== null ? this.getPickerState(pickerStage) : null;
3320
+ const isPickerReached = pickerState?.reached ?? false;
3321
+ const hasGifts = this.gifts.length > 0 || (isPickerReached && this.getPickerCards(pickerStage).length > 0);
3322
+ // The gift tier is reached but none of this offer's gifts is in the cart: the engine
3323
+ // grants that tier only once one is, and pays nothing until then (no fallback to a lower
3324
+ // tier). So neither "congrats" nor "conditions unmet" is true — ask for the pick. Only
3325
+ // once the cart lines are read, or a cart that holds the gift would flash this line.
3326
+ const awaitsGiftPick = isPickerReached && this.cartLinesLoaded && pickerState.freeCount === 0;
3327
+ // "You got N free products" replaces congrats only when the gift tier is the offer's last:
3328
+ // below a higher tier, `guidance` (or congrats for that higher tier) is the truer line.
3329
+ const gotGifts = progress.allMet && isPickerReached && pickerState.freeCount > 0
3330
+ && stages[stages.length - 1] === pickerStage;
3331
+ return (index.h(index.Host, { class: "s-conditional-offer-root" }, index.h("div", { class: "s-conditional-offer-banner" }, index.h("div", { class: "s-conditional-offer-lead" }, this.renderBadges(stages, progress), index.h("div", { class: "s-conditional-offer-text" }, index.h("div", { class: "s-conditional-offer-heading", onMouseEnter: () => (this.isTooltipHovered = true), onMouseLeave: () => (this.isTooltipHovered = false) }, index.h("span", { class: "s-conditional-offer-name" }, this.offer.title), index.h("span", { class: "s-conditional-offer-info", ref: (el) => (this.infoAnchorEl = el) }, index.h("button", { type: "button", class: "s-conditional-offer-info-trigger", ref: (el) => (this.infoTriggerEl = el), "aria-label": detailsTitle, title: detailsTitle, "aria-expanded": this.isTooltipOpen ? 'true' : 'false', "aria-describedby": this.elementId('tooltip'), onClick: () => this.toggleTooltip() }, index.h(InformationIcon, { class: "s-conditional-offer-info-icon" })), this.isTooltipOpen ? this.renderTooltip(stages, progress) : null)), awaitsGiftPick || gotGifts ? (index.h("div", { class: "s-conditional-offer-message", role: "status" }, this.pickerGotMessage(pickerState.limit, awaitsGiftPick))) : this.areGiftsUnavailable(stages, progress) ? (index.h("div", { class: "s-conditional-offer-message", role: "status" }, this.trans('free_products_unavailable'))) : progress.allMet ? (index.h("div", { class: "s-conditional-offer-message", role: "status" }, this.trans('congrats'))) : guidance ? (
2723
3332
  // innerHTML: the `:remaining` half carries `salla.money()` markup;
2724
3333
  // the reward half is escaped in `getGuidanceMessage`.
2725
- index.h("div", { class: "s-conditional-offer-message", innerHTML: guidance })) : null)), hasGifts ? this.renderGiftsToggle('inline') : null), hasGifts && this.isGiftsOpen ? this.renderGiftsCard() : null, hasGifts ? this.renderGiftsToggle('bar') : null));
3334
+ index.h("div", { class: "s-conditional-offer-message", innerHTML: guidance })) : null)), hasGifts ? this.renderGiftsToggle('inline', !!pickerStage) : null), hasGifts && this.isGiftsOpen ? this.renderGiftsCard(pickerStage) : null, hasGifts ? this.renderGiftsToggle('bar', !!pickerStage) : null));
2726
3335
  }
2727
3336
  get hostEl() { return index.getElement(this); }
2728
3337
  };