@liquidcommercedev/rmn-sdk 1.4.6 → 1.5.0-beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. package/dist/index.cjs +2424 -721
  2. package/dist/index.esm.js +2424 -722
  3. package/dist/types/common/helpers/uuid.helper.d.ts +49 -0
  4. package/dist/types/enums.d.ts +59 -1
  5. package/dist/types/index.umd.d.ts +2 -2
  6. package/dist/types/modules/element/component/carousel/carousel.component.d.ts +3 -0
  7. package/dist/types/modules/element/component/carousel/carousel.interface.d.ts +35 -0
  8. package/dist/types/modules/element/component/carousel/carousel.style.d.ts +2 -0
  9. package/dist/types/modules/element/component/carousel/index.d.ts +3 -0
  10. package/dist/types/modules/element/component/spot/index.d.ts +2 -0
  11. package/dist/types/modules/element/component/spot/spot.component.d.ts +3 -0
  12. package/dist/types/modules/element/component/spot/spot.interface.d.ts +10 -0
  13. package/dist/types/modules/element/component/utils.d.ts +1 -0
  14. package/dist/types/modules/{spot/html/constants/html.constant.d.ts → element/element.constant.d.ts} +2 -5
  15. package/dist/types/modules/element/element.interface.d.ts +52 -0
  16. package/dist/types/modules/element/element.service.d.ts +40 -0
  17. package/dist/types/modules/element/index.d.ts +3 -0
  18. package/dist/types/modules/element/template/helper.d.ts +4 -0
  19. package/dist/types/modules/element/template/index.d.ts +1 -0
  20. package/dist/types/modules/element/template/reservebar/collection-banner-without-text-block.template.d.ts +3 -0
  21. package/dist/types/modules/element/template/reservebar/homepage-hero-full-image.template.d.ts +3 -0
  22. package/dist/types/modules/element/template/reservebar/homepage-hero-three-tile.template.d.ts +3 -0
  23. package/dist/types/modules/element/template/reservebar/homepage-hero-two-tile.template.d.ts +3 -0
  24. package/dist/types/modules/element/template/reservebar/large-category-image-tout.template.d.ts +3 -0
  25. package/dist/types/modules/element/template/reservebar/navigation-banner.template.d.ts +3 -0
  26. package/dist/types/modules/element/template/reservebar/small-category-image-tout.template.d.ts +3 -0
  27. package/dist/types/modules/element/template/reservebar/small-discover-tout.template.d.ts +3 -0
  28. package/dist/types/modules/element/template/template.service.d.ts +11 -0
  29. package/dist/types/modules/element/template/template.type.d.ts +11 -0
  30. package/dist/types/modules/event/event.constant.d.ts +1 -0
  31. package/dist/types/modules/event/event.interface.d.ts +72 -0
  32. package/dist/types/modules/event/event.service.d.ts +45 -0
  33. package/dist/types/modules/event/helpers/index.d.ts +3 -0
  34. package/dist/types/modules/event/helpers/intersection.service.d.ts +8 -0
  35. package/dist/types/modules/event/helpers/localstorage.service.d.ts +26 -0
  36. package/dist/types/modules/event/helpers/resize.service.d.ts +30 -0
  37. package/dist/types/modules/event/index.d.ts +4 -0
  38. package/dist/types/modules/event/pubsub.d.ts +69 -0
  39. package/dist/types/modules/selection/index.d.ts +4 -0
  40. package/dist/types/modules/selection/selection.constant.d.ts +1 -0
  41. package/dist/types/modules/{spot/spot.interface.d.ts → selection/selection.interface.d.ts} +10 -13
  42. package/dist/types/modules/selection/selection.service.d.ts +18 -0
  43. package/dist/types/modules/{spot/spot.type.d.ts → selection/selection.type.d.ts} +4 -3
  44. package/dist/types/rmn-client.d.ts +66 -23
  45. package/dist/types/static.constant.d.ts +4 -0
  46. package/dist/types/types.d.ts +17 -6
  47. package/package.json +2 -2
  48. package/umd/liquidcommerce-rmn-sdk.min.js +1 -1
  49. package/dist/types/modules/spot/html/constants/index.d.ts +0 -1
  50. package/dist/types/modules/spot/html/index.d.ts +0 -1
  51. package/dist/types/modules/spot/html/spot.element.service.d.ts +0 -7
  52. package/dist/types/modules/spot/html/templates/index.d.ts +0 -1
  53. package/dist/types/modules/spot/html/templates/reservebar/collection-banner-without-text-block.template.d.ts +0 -2
  54. package/dist/types/modules/spot/html/templates/reservebar/homepage-hero-full-image.template.d.ts +0 -2
  55. package/dist/types/modules/spot/html/templates/reservebar/homepage-hero-three-tile.template.d.ts +0 -2
  56. package/dist/types/modules/spot/html/templates/reservebar/homepage-hero-two-tile.template.d.ts +0 -2
  57. package/dist/types/modules/spot/html/templates/reservebar/large-category-image-tout.template.d.ts +0 -2
  58. package/dist/types/modules/spot/html/templates/reservebar/navigation-banner.template.d.ts +0 -2
  59. package/dist/types/modules/spot/html/templates/reservebar/small-category-image-tout.template.d.ts +0 -2
  60. package/dist/types/modules/spot/html/templates/reservebar/small-discover-tout.template.d.ts +0 -2
  61. package/dist/types/modules/spot/html/templates/spot.template.d.ts +0 -21
  62. package/dist/types/modules/spot/index.d.ts +0 -6
  63. package/dist/types/modules/spot/spot.constant.d.ts +0 -4
  64. package/dist/types/modules/spot/spot.enum.d.ts +0 -57
  65. package/dist/types/modules/spot/spot.html.service.d.ts +0 -22
  66. package/dist/types/modules/spot/spot.selection.service.d.ts +0 -15
  67. /package/dist/types/modules/{spot/html/templates → element/template}/iab/billboard/billboard-v1.template.d.ts +0 -0
  68. /package/dist/types/modules/{spot/html/templates → element/template}/iab/billboard/billboard-v2.template.d.ts +0 -0
  69. /package/dist/types/modules/{spot/html/templates → element/template}/iab/billboard/billboard-v3.template.d.ts +0 -0
  70. /package/dist/types/modules/{spot/html/templates → element/template}/iab/billboard/index.d.ts +0 -0
  71. /package/dist/types/modules/{spot/html/templates → element/template}/iab/in-text/in-text-v1.template.d.ts +0 -0
  72. /package/dist/types/modules/{spot/html/templates → element/template}/iab/in-text/index.d.ts +0 -0
  73. /package/dist/types/modules/{spot/html/templates → element/template}/iab/index.d.ts +0 -0
  74. /package/dist/types/modules/{spot/html/templates → element/template}/iab/large-leaderboard/index.d.ts +0 -0
  75. /package/dist/types/modules/{spot/html/templates → element/template}/iab/large-leaderboard/large-leaderboard-v1.template.d.ts +0 -0
  76. /package/dist/types/modules/{spot/html/templates → element/template}/iab/large-leaderboard/large-leaderboard-v2.template.d.ts +0 -0
  77. /package/dist/types/modules/{spot/html/templates → element/template}/iab/large-rectangle/index.d.ts +0 -0
  78. /package/dist/types/modules/{spot/html/templates → element/template}/iab/large-rectangle/large-rectangle-v1.template.d.ts +0 -0
  79. /package/dist/types/modules/{spot/html/templates → element/template}/iab/square/index.d.ts +0 -0
  80. /package/dist/types/modules/{spot/html/templates → element/template}/iab/square/square-v1.template.d.ts +0 -0
  81. /package/dist/types/modules/{spot/html/templates → element/template}/iab/square/square-v2.template.d.ts +0 -0
  82. /package/dist/types/modules/{spot/html/templates → element/template}/iab/vertical-rectangle/index.d.ts +0 -0
  83. /package/dist/types/modules/{spot/html/templates → element/template}/iab/vertical-rectangle/vertical-rectangle-v1.template.d.ts +0 -0
  84. /package/dist/types/modules/{spot/html/templates → element/template}/iab/wide-skyscraper/index.d.ts +0 -0
  85. /package/dist/types/modules/{spot/html/templates → element/template}/iab/wide-skyscraper/wide-skyscraper-v1.template.d.ts +0 -0
  86. /package/dist/types/modules/{spot/html/templates → element/template}/reservebar/index.d.ts +0 -0
package/dist/index.cjs CHANGED
@@ -44,6 +44,7 @@ exports.RMN_SPOT_TYPE = void 0;
44
44
  exports.RMN_FILTER_PROPERTIES = void 0;
45
45
  (function (RMN_FILTER_PROPERTIES) {
46
46
  RMN_FILTER_PROPERTIES["KEYWORDS"] = "keywords";
47
+ RMN_FILTER_PROPERTIES["PAGE_LOCATION"] = "pageLocation";
47
48
  RMN_FILTER_PROPERTIES["PARENTCO"] = "parentCo";
48
49
  RMN_FILTER_PROPERTIES["BRAND"] = "brand";
49
50
  RMN_FILTER_PROPERTIES["CATEGORY"] = "category";
@@ -55,6 +56,7 @@ exports.RMN_FILTER_PROPERTIES = void 0;
55
56
  })(exports.RMN_FILTER_PROPERTIES || (exports.RMN_FILTER_PROPERTIES = {}));
56
57
  exports.RMN_SPOT_EVENT = void 0;
57
58
  (function (RMN_SPOT_EVENT) {
59
+ RMN_SPOT_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
58
60
  RMN_SPOT_EVENT["IMPRESSION"] = "IMPRESSION";
59
61
  RMN_SPOT_EVENT["CLICK"] = "CLICK";
60
62
  RMN_SPOT_EVENT["PURCHASE"] = "PURCHASE";
@@ -62,7 +64,6 @@ exports.RMN_SPOT_EVENT = void 0;
62
64
  RMN_SPOT_EVENT["ADD_TO_WISHLIST"] = "ADD_TO_WISHLIST";
63
65
  RMN_SPOT_EVENT["BUY_NOW"] = "BUY_NOW";
64
66
  })(exports.RMN_SPOT_EVENT || (exports.RMN_SPOT_EVENT = {}));
65
-
66
67
  exports.RMN_ENV = void 0;
67
68
  (function (RMN_ENV) {
68
69
  RMN_ENV["LOCAL"] = "local";
@@ -14999,7 +15000,7 @@ class BaseApi extends BaseApiAbstract {
14999
15000
  */
15000
15001
  async post(path, data, configOverrides) {
15001
15002
  let requestData = data;
15002
- if (this.authInfo.env !== exports.RMN_ENV.DEVELOPMENT) {
15003
+ if (![exports.RMN_ENV.LOCAL, exports.RMN_ENV.DEVELOPMENT].includes(this.authInfo.env)) {
15003
15004
  const timestamp = new Date().getTime();
15004
15005
  configOverrides = {
15005
15006
  ...configOverrides,
@@ -15152,9 +15153,7 @@ class AuthService extends BaseApi {
15152
15153
  }
15153
15154
 
15154
15155
  const SPOT_ELEMENT_TAG = 'spot-element';
15155
- const SPOT_ELEMENT_SLOT_NAME = `${SPOT_ELEMENT_TAG}-custom-content`;
15156
- const SPOTS_SELECTION_API_PATH = '/spots/selection';
15157
-
15156
+ const CAROUSEL_ELEMENT_TAG = 'spot-carousel-element';
15158
15157
  const GFONT_PRECONNECT = `
15159
15158
  <link rel="preconnect" href="https://fonts.googleapis.com">
15160
15159
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -15165,141 +15164,1156 @@ const GFONT_SOURCE_SANS_3 = `
15165
15164
  const GFONT_CORMORANT = `
15166
15165
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant:ital,wght@0,300..700;1,300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap">
15167
15166
  `;
15168
- const SPOT_ELEMENT_TEMPLATE = (width, height, hasCustomContent) => {
15169
- const style = document.createElement('style');
15170
- style.textContent = `
15171
- :host {
15172
- display: block;
15173
- position: relative;
15174
- container-type: inline-size;
15175
- overflow: hidden;
15167
+
15168
+ class IntersectionObserverService {
15169
+ constructor(defaultOptions = {}) {
15170
+ this.observers = new Map();
15171
+ this.defaultOptions = {
15172
+ root: null,
15173
+ rootMargin: '0px',
15174
+ threshold: 0.5,
15175
+ ...defaultOptions,
15176
+ };
15176
15177
  }
15177
- :host([fluid="true"]) {
15178
- width: 100%;
15179
- height: 100%;
15178
+ observe(element, callback, options = {}) {
15179
+ const mergedOptions = { ...this.defaultOptions, ...options };
15180
+ const ioCallback = (entries) => {
15181
+ entries.forEach((entry) => {
15182
+ if (entry.isIntersecting) {
15183
+ callback(entry);
15184
+ }
15185
+ });
15186
+ };
15187
+ const observer = new IntersectionObserver(ioCallback, mergedOptions);
15188
+ this.observers.set(element, observer);
15189
+ observer.observe(element);
15190
+ }
15191
+ unobserve(element) {
15192
+ const observer = this.observers.get(element);
15193
+ if (observer) {
15194
+ observer.unobserve(element);
15195
+ observer.disconnect();
15196
+ this.observers.delete(element);
15197
+ }
15180
15198
  }
15181
- :host([fluid="false"]) {
15182
- width: ${width}px;
15183
- height: ${height}px;
15199
+ unobserveAll() {
15200
+ this.observers.forEach((observer, element) => {
15201
+ observer.unobserve(element);
15202
+ observer.disconnect();
15203
+ });
15204
+ this.observers.clear();
15184
15205
  }
15185
- `;
15186
- const wrapper = document.createElement('div');
15187
- wrapper.className = 'wrapper';
15188
- const slot = document.createElement('slot');
15189
- slot.name = SPOT_ELEMENT_SLOT_NAME;
15190
- if (!hasCustomContent) {
15191
- style.textContent += `
15192
- :host .wrapper {
15193
- position: absolute;
15194
- top: 0;
15195
- left: 0;
15206
+ }
15207
+
15208
+ class LocalStorage {
15209
+ constructor() {
15210
+ this.spots = new Map();
15211
+ // Sync local storage with the current state
15212
+ this.syncLocalStorage();
15213
+ // Remove expired spots
15214
+ this.removeExpiredSpots();
15215
+ }
15216
+ static getInstance() {
15217
+ if (!LocalStorage.instance) {
15218
+ LocalStorage.instance = new LocalStorage();
15219
+ }
15220
+ return LocalStorage.instance;
15221
+ }
15222
+ syncLocalStorage() {
15223
+ const localStorageData = localStorage.getItem(LocalStorage.localStorageKey);
15224
+ // TODO: Encrypt the data before storing it in the local storage
15225
+ if (localStorageData) {
15226
+ try {
15227
+ const parsedData = JSON.parse(localStorageData);
15228
+ if (parsedData && typeof parsedData === 'object') {
15229
+ this.spots = this.objToMap(parsedData);
15230
+ }
15231
+ else {
15232
+ this.clearLocalStorage();
15233
+ }
15234
+ }
15235
+ catch (_a) {
15236
+ // If there is an error parsing the data, clear the local storage to prevent any issues
15237
+ this.clearLocalStorage();
15238
+ }
15239
+ }
15240
+ }
15241
+ setSpot(spotId, data) {
15242
+ data.createdAt = Date.now();
15243
+ this.spots.set(spotId, data);
15244
+ this.updateLocalStorage();
15245
+ }
15246
+ getSpot(spotId) {
15247
+ return this.spots.get(spotId);
15248
+ }
15249
+ removeSpot(spotId) {
15250
+ this.spots.delete(spotId);
15251
+ this.updateLocalStorage();
15252
+ }
15253
+ updateLocalStorage() {
15254
+ const data = this.mapToObj(this.spots);
15255
+ localStorage.setItem(LocalStorage.localStorageKey, JSON.stringify(data));
15256
+ }
15257
+ clearLocalStorage() {
15258
+ localStorage.removeItem(LocalStorage.localStorageKey);
15259
+ }
15260
+ removeExpiredSpots() {
15261
+ const currentTime = Date.now();
15262
+ this.spots.forEach((spot, spotId) => {
15263
+ var _a;
15264
+ if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorage.spotExpirationTime) {
15265
+ this.spots.delete(spotId);
15266
+ }
15267
+ });
15268
+ this.updateLocalStorage();
15269
+ }
15270
+ mapToObj(map) {
15271
+ return Object.fromEntries(map);
15272
+ }
15273
+ objToMap(obj) {
15274
+ return new Map(Object.entries(obj));
15275
+ }
15276
+ }
15277
+ LocalStorage.localStorageKey = 'lc_rmn';
15278
+ LocalStorage.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
15279
+
15280
+ class ResizeObserverService {
15281
+ constructor({ element, maxSize, minScale }) {
15282
+ this.element = element;
15283
+ if (!element.parentElement) {
15284
+ throw new Error('RmnSdk: Spot element must have a parent container.');
15285
+ }
15286
+ this.container = element.parentElement;
15287
+ this.setDimensions(maxSize, minScale);
15288
+ this.resizeObserver = new ResizeObserver(() => this.updateElementSize());
15289
+ this.resizeObserver.observe(this.container);
15290
+ // Initial size update
15291
+ this.updateElementSize();
15292
+ }
15293
+ setDimensions(maxSize, minScale) {
15294
+ if (minScale <= 0 || minScale > 1) {
15295
+ throw new Error('RmnSdk: Invalid minScale value');
15296
+ }
15297
+ const minSize = {
15298
+ width: maxSize.width * minScale,
15299
+ height: maxSize.height * minScale,
15300
+ };
15301
+ if (maxSize.width <= 0 || maxSize.height <= 0 || minSize.width <= 0 || minSize.height <= 0) {
15302
+ throw new Error('RmnSdk: Invalid dimensions');
15303
+ }
15304
+ if (minSize.width > maxSize.width || minSize.height > maxSize.height) {
15305
+ throw new Error('RmnSdk: Minimum size cannot be greater than maximum size');
15306
+ }
15307
+ this.maxSize = maxSize;
15308
+ this.minSize = minSize;
15309
+ this.aspectRatio = this.maxSize.width / this.maxSize.height;
15310
+ }
15311
+ updateElementSize() {
15312
+ const { clientWidth: containerWidth, clientHeight: containerHeight } = this.container;
15313
+ let newWidth;
15314
+ let newHeight;
15315
+ // First, try to fit the maximum width
15316
+ newWidth = Math.min(containerWidth, this.maxSize.width);
15317
+ newHeight = newWidth / this.aspectRatio;
15318
+ // If the height exceeds the container, adjust based on height
15319
+ if (newWidth > containerWidth) {
15320
+ newWidth = containerWidth;
15321
+ newHeight = containerHeight * this.aspectRatio;
15322
+ }
15323
+ // Ensure we're not going below minimum dimensions
15324
+ newWidth = Math.max(newWidth, this.minSize.width);
15325
+ newHeight = Math.max(newHeight, this.minSize.height);
15326
+ this.element.style.width = `${newWidth}px`;
15327
+ this.element.style.height = `${newHeight}px`;
15328
+ // Calculate the scale percentage
15329
+ const scaleWidth = newWidth / this.maxSize.width;
15330
+ const scaleHeight = newHeight / this.maxSize.height;
15331
+ const scale = Math.min(scaleWidth, scaleHeight);
15332
+ // Dispatch a custom event
15333
+ this.element.dispatchEvent(new CustomEvent('spotSizeChanged', {
15334
+ detail: {
15335
+ width: this.maxSize.width,
15336
+ height: this.maxSize.height,
15337
+ newWidth,
15338
+ newHeight,
15339
+ scale,
15340
+ },
15341
+ }));
15342
+ }
15343
+ disconnect() {
15344
+ this.resizeObserver.disconnect();
15345
+ }
15346
+ }
15347
+
15348
+ function calculateScaleFactor(elementScale) {
15349
+ // Step 1: Apply square root for non-linear scaling
15350
+ // This creates a more gradual scaling effect, especially for larger changes
15351
+ // For example:
15352
+ // - elementScale of 0.25 (1/4 size) becomes 0.5
15353
+ // - elementScale of 1 (unchanged) remains 1
15354
+ // - elementScale of 4 (4x size) becomes 2
15355
+ const baseFactor = Math.sqrt(elementScale);
15356
+ // Step 2: Apply additional dampening to further soften the scaling effect
15357
+ // The dampening factor (0.5) can be adjusted:
15358
+ // - Lower values (closer to 0) make scaling more subtle
15359
+ // - Higher values (closer to 1) make scaling more pronounced
15360
+ const dampening = 0.35;
15361
+ // Calculate the scaleFactor:
15362
+ // 1. (baseFactor - 1) represents the change from the original size
15363
+ // 2. Multiply by dampening to reduce the effect
15364
+ // 3. Add 1 to center the scaling around the original size
15365
+ // For example, if baseFactor is 2:
15366
+ // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
15367
+ const scaleFactor = 1 + (baseFactor - 1) * dampening;
15368
+ // Step 3: Define the allowed range for the scale factor
15369
+ // This ensures that the font size never changes too drastically
15370
+ const minScale = 0.35; // Font will never be smaller than 50% of original
15371
+ const maxScale = 1.5; // Font will never be larger than 150% of original
15372
+ // Step 4: Clamp the scale factor to the defined range
15373
+ // Math.min ensures the value doesn't exceed maxScale
15374
+ // Math.max ensures the value isn't less than minScale
15375
+ return Math.max(minScale, Math.min(maxScale, scaleFactor));
15376
+ }
15377
+
15378
+ const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15379
+ :host {
15380
+ position: relative;
15381
+ display: inline-block;
15382
+ margin: 0;
15383
+ overflow: hidden;
15384
+ width: ${fluid ? '100%' : `${width}px`};
15385
+ height: ${fluid ? '100%' : `${height}px`};
15386
+ }
15387
+
15388
+ .slides {
15389
+ position: relative;
15390
+ height: 100%;
15196
15391
  width: 100%;
15392
+ }
15393
+
15394
+ .slide {
15395
+ display: none;
15396
+
15397
+ justify-content: center;
15398
+ align-items: center;
15197
15399
  height: 100%;
15198
- overflow: hidden;
15199
- }
15200
- `;
15400
+ width: 100%;
15201
15401
  }
15202
- return { style, wrapper, slot };
15203
- };
15204
15402
 
15205
- const STYLES$i = ({ primaryImage, secondaryImage }) => `
15206
- <style>
15207
- .content {
15208
- width: 100%;
15209
- height: 100%;
15210
- display: flex;
15211
- flex-direction: row;
15212
- background-color: #FFFFFF;
15213
- gap: 0.5cqw;
15214
- cursor: pointer;
15215
- color: inherit;
15216
- }
15217
- .big-image {
15218
- width: 65%;
15219
- height: 100%;
15220
- background-image: url("${primaryImage}");
15221
- background-position: center;
15222
- background-repeat: no-repeat;
15223
- background-size: cover;
15403
+ .slide.active {
15404
+ display: flex;
15405
+ }
15406
+
15407
+ .dots {
15408
+ position: absolute;
15409
+ display: flex;
15410
+ align-items: center;
15411
+ gap: 8px;
15412
+ opacity: var(--opacity, 1);
15413
+ }
15414
+
15415
+ .dots.small .dot {
15416
+ width: 8px;
15417
+ height: 8px;
15418
+ }
15419
+
15420
+ .dots.base .dot {
15421
+ width: 12px;
15422
+ height: 12px;
15423
+ }
15424
+
15425
+ .dots.large .dot {
15426
+ width: 16px;
15427
+ height: 16px;
15428
+ }
15429
+
15430
+ .dots .dot {
15431
+ border-radius: 50%;
15432
+ cursor: pointer;
15433
+ transition: all 0.3s ease;
15434
+ }
15435
+
15436
+ .dots.top-left,
15437
+ .dots.bottom-left {
15438
+ left: 10px;
15439
+ }
15440
+
15441
+ .dots.top-center,
15442
+ .dots.bottom-center {
15443
+ left: 50%;
15444
+ transform: translateX(-50%);
15445
+ }
15446
+
15447
+ .dots.top-right,
15448
+ .dots.bottom-right {
15449
+ right: 10px;
15450
+ }
15451
+
15452
+ .dots.top-left,
15453
+ .dots.top-center,
15454
+ .dots.top-right {
15455
+ top: 10px;
15456
+ }
15457
+
15458
+ .dots.bottom-left,
15459
+ .dots.bottom-center,
15460
+ .dots.bottom-right {
15461
+ bottom: 10px;
15462
+ }
15463
+
15464
+ .dots.middle-left {
15465
+ left: 10px;
15466
+ top: 50%;
15467
+ transform: translateY(-50%);
15468
+ flex-direction: column;
15469
+ }
15470
+
15471
+ .dots.middle-right {
15472
+ right: 10px;
15473
+ top: 50%;
15474
+ transform: translateY(-50%);
15475
+ flex-direction: column;
15476
+ }
15477
+
15478
+ .buttons {
15479
+ opacity: var(--opacity, 1);
15480
+ }
15481
+
15482
+ .buttons button {
15483
+ background-color: #00000080;
15484
+ color: #fff;
15485
+ border: none;
15486
+ padding: 10px;
15487
+ cursor: pointer;
15488
+ transition: background-color 0.3s ease;
15489
+ }
15490
+
15491
+ .buttons.small button {
15492
+ padding: 6px;
15493
+ }
15494
+
15495
+ .buttons.base button {
15496
+ padding: 10px;
15497
+ }
15498
+
15499
+ .buttons.large button {
15500
+ padding: 14px;
15501
+ }
15502
+
15503
+ .buttons button:hover {
15504
+ background-color: #000000b3;
15505
+ }
15506
+
15507
+ .buttons.buttons-separate button {
15508
+ position: absolute;
15509
+ top: 50%;
15510
+ transform: translateY(-50%);
15511
+ }
15512
+
15513
+ .buttons.buttons-separate .prev-button {
15514
+ left: 10px;
15515
+ }
15516
+
15517
+ .buttons.buttons-separate .next-button {
15518
+ right: 10px;
15519
+ }
15520
+
15521
+ .buttons.buttons-together {
15522
+ position: absolute;
15523
+ display: flex;
15524
+ gap: 10px;
15525
+ }
15526
+
15527
+ .buttons.buttons-together.top-left,
15528
+ .buttons.buttons-together.bottom-left {
15529
+ left: 10px;
15530
+ }
15531
+
15532
+ .buttons.buttons-together.top-center,
15533
+ .buttons.buttons-together.bottom-center {
15534
+ left: 50%;
15535
+ transform: translateX(-50%);
15536
+ }
15537
+
15538
+ .buttons.buttons-together.top-right,
15539
+ .buttons.buttons-together.bottom-right {
15540
+ right: 10px;
15541
+ }
15542
+
15543
+ .buttons.buttons-together.top-left,
15544
+ .buttons.buttons-together.top-center,
15545
+ .buttons.buttons-together.top-right {
15546
+ top: 10px;
15547
+ }
15548
+
15549
+ .buttons.buttons-together.bottom-left,
15550
+ .buttons.buttons-together.bottom-center,
15551
+ .buttons.buttons-together.bottom-right {
15552
+ bottom: 10px;
15553
+ }
15554
+
15555
+ .buttons.buttons-together.middle-left,
15556
+ .buttons.buttons-together.middle-right {
15557
+ top: 50%;
15558
+ transform: translateY(-50%);
15559
+ flex-direction: column;
15560
+ }
15561
+
15562
+ .buttons.buttons-together.middle-left {
15563
+ left: 10px;
15564
+ }
15565
+
15566
+ .buttons.buttons-together.middle-right {
15567
+ right: 10px;
15568
+ }
15569
+
15570
+ @media (max-width: 768px) {
15571
+ .buttons button {
15572
+ padding: 8px 12px;
15573
+ font-size: 14px;
15224
15574
  }
15225
- .main {
15226
- width: 35%;
15227
- height: 100%;
15228
- display: flex;
15229
- flex-direction: column;
15230
- gap: 0.5cqw;
15575
+ }
15576
+ `;
15577
+
15578
+ let CarouselElement;
15579
+ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') {
15580
+ class CustomCarouselElement extends HTMLElement {
15581
+ constructor() {
15582
+ super();
15583
+ this.currentSlide = 0;
15584
+ this.dotElements = [];
15585
+ this.prevButton = null;
15586
+ this.nextButton = null;
15587
+ this.autoplayInterval = null;
15588
+ this.useDots = false;
15589
+ this.useButtons = false;
15590
+ this.originalFontSizes = new Map();
15591
+ this.attachShadow({ mode: 'open' });
15231
15592
  }
15232
- .small-image {
15233
- width: 100%;
15234
- height: 50%;
15235
- background-image: url("${secondaryImage}");
15236
- background-position: center;
15237
- background-repeat: no-repeat;
15238
- background-size: cover;
15593
+ connectedCallback() {
15594
+ this.initializeOptions();
15595
+ this.setupResizeObserver();
15596
+ this.render();
15597
+ this.setupCarousel();
15239
15598
  }
15240
- .text {
15241
- background: #E8E6DE;
15242
- text-align: center;
15243
- display: flex;
15244
- flex-direction: column;
15245
- justify-content: center;
15246
- align-items: center;
15247
- height: 50%;
15248
- width: 100%;
15249
- padding: 5% 10%;
15250
- box-sizing: border-box;
15251
- font-family: "Source Sans 3", sans-serif;
15599
+ disconnectedCallback() {
15600
+ var _a;
15601
+ this.stopAutoplay();
15602
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
15252
15603
  }
15253
- .header {
15254
- font-size: 2.2cqw;
15255
- color: #000000;
15256
- font-style: normal;
15257
- font-weight: 400;
15258
- font-family: "Cormorant", serif;
15259
- margin: 0;
15604
+ initializeOptions() {
15605
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
15606
+ this.useDots = ((_a = this.data) === null || _a === void 0 ? void 0 : _a.useDots) === true || typeof ((_b = this.data) === null || _b === void 0 ? void 0 : _b.useDots) === 'object';
15607
+ this.useButtons = ((_c = this.data) === null || _c === void 0 ? void 0 : _c.useButtons) === true || typeof ((_d = this.data) === null || _d === void 0 ? void 0 : _d.useButtons) === 'object';
15608
+ this.autoplay = (_f = (_e = this.data) === null || _e === void 0 ? void 0 : _e.autoplay) !== null && _f !== void 0 ? _f : true;
15609
+ this.interval = (_h = (_g = this.data) === null || _g === void 0 ? void 0 : _g.interval) !== null && _h !== void 0 ? _h : CustomCarouselElement.defaultInterval;
15610
+ this.dotsOptions = {
15611
+ position: 'bottom-center',
15612
+ color: '#d9d9d9',
15613
+ activeColor: '#b5914a',
15614
+ size: 'base',
15615
+ opacity: 1,
15616
+ ...(typeof ((_j = this.data) === null || _j === void 0 ? void 0 : _j.useDots) === 'object' ? this.data.useDots : {}),
15617
+ };
15618
+ this.buttonsOptions = {
15619
+ together: false,
15620
+ position: 'middle-sides',
15621
+ textColor: '#000000',
15622
+ backgroundColor: '#ffffff',
15623
+ borderRadius: '50%',
15624
+ prev: 'Prev',
15625
+ next: 'Next',
15626
+ size: 'base',
15627
+ opacity: 1,
15628
+ ...(typeof ((_k = this.data) === null || _k === void 0 ? void 0 : _k.useButtons) === 'object' ? this.data.useButtons : {}),
15629
+ };
15630
+ this.validateOptions();
15260
15631
  }
15261
- .description {
15262
- font-size: 1.2cqw;
15263
- color: #000000;
15264
- font-style: normal;
15265
- font-weight: 400;
15266
- margin: 1cqh 0;
15632
+ setupResizeObserver() {
15633
+ if (this.data && !this.data.fluid) {
15634
+ this.resizeObserver = new ResizeObserverService({
15635
+ element: this,
15636
+ maxSize: {
15637
+ width: this.data.width,
15638
+ height: this.data.height,
15639
+ },
15640
+ minScale: this.data.minScale,
15641
+ });
15642
+ this.addEventListener('spotSizeChanged', this.handleCarouselSizeChanged.bind(this));
15643
+ }
15267
15644
  }
15268
- .button {
15269
- font-size: 1cqw;
15270
- color: #000000;
15271
- background-color: transparent;
15272
- font-style: normal;
15273
- font-weight: 700;
15274
- text-decoration: underline;
15275
- text-transform: uppercase;
15276
- border: none;
15277
- cursor: pointer;
15278
- padding: 0;
15279
- transition: opacity 0.3s ease;
15645
+ handleCarouselSizeChanged(event) {
15646
+ const isRBSpot = 'rbHeroadaksda'.startsWith('rb');
15647
+ if (!isRBSpot) {
15648
+ // Adjust text elements font size based on the scale factor
15649
+ this.adjustFontSize(event.detail.scale);
15650
+ }
15280
15651
  }
15281
- .content:hover .button {
15282
- opacity: 0.7;
15652
+ adjustFontSize(elementScale) {
15653
+ var _a;
15654
+ const scaleFactor = calculateScaleFactor(elementScale);
15655
+ // Find all text elements within the shadow root
15656
+ const elements = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('h1, h2, h3, h4, p, span');
15657
+ elements === null || elements === void 0 ? void 0 : elements.forEach((element) => {
15658
+ if (element instanceof HTMLElement) {
15659
+ if (!this.originalFontSizes.has(element)) {
15660
+ const originalSize = parseFloat(window.getComputedStyle(element).fontSize);
15661
+ this.originalFontSizes.set(element, originalSize);
15662
+ }
15663
+ const originalSize = this.originalFontSizes.get(element);
15664
+ const newFontSize = originalSize * scaleFactor;
15665
+ element.style.fontSize = `${newFontSize}px`;
15666
+ }
15667
+ });
15283
15668
  }
15284
- @container (max-width: 600px) {
15285
- .header {
15286
- font-size: 3cqw;
15669
+ render() {
15670
+ var _a;
15671
+ if (!this.shadowRoot)
15672
+ return;
15673
+ const style = document.createElement('style');
15674
+ style.textContent = CAROUSEL_COMPONENT_STYLE(this.data);
15675
+ this.shadowRoot.appendChild(style);
15676
+ const slides = this.renderSlides();
15677
+ this.shadowRoot.appendChild(slides);
15678
+ this.slidesContainer = (_a = this.shadowRoot.querySelector('.slides')) !== null && _a !== void 0 ? _a : undefined;
15679
+ if (this.useDots) {
15680
+ const dots = this.renderDots();
15681
+ if (dots)
15682
+ this.shadowRoot.appendChild(dots);
15287
15683
  }
15288
- .description {
15289
- font-size: 1.6cqw;
15290
- margin: 0;
15684
+ if (this.useButtons) {
15685
+ const buttons = this.renderButtons();
15686
+ if (buttons)
15687
+ this.shadowRoot.appendChild(buttons);
15291
15688
  }
15292
- .button {
15293
- font-size: 1.4cqw;
15689
+ }
15690
+ setupCarousel() {
15691
+ this.setupDots();
15692
+ this.setupButtons();
15693
+ if (this.autoplay) {
15694
+ this.startAutoplay();
15294
15695
  }
15696
+ this.updateCarousel();
15295
15697
  }
15296
- </style>
15297
- `;
15298
- function billboardV1Template(spot) {
15299
- return `
15300
- ${GFONT_PRECONNECT}
15301
- ${GFONT_SOURCE_SANS_3}
15302
- ${GFONT_CORMORANT}
15698
+ renderSlides() {
15699
+ const slidesContainer = document.createElement('div');
15700
+ slidesContainer.className = 'slides';
15701
+ this.slides.forEach((slide, index) => {
15702
+ const slideElement = document.createElement('div');
15703
+ slideElement.className = `slide ${index === this.currentSlide ? 'active' : ''}`;
15704
+ if (slide instanceof HTMLElement) {
15705
+ slideElement.appendChild(slide);
15706
+ }
15707
+ slidesContainer.appendChild(slideElement);
15708
+ });
15709
+ return slidesContainer;
15710
+ }
15711
+ renderDots() {
15712
+ const dotsContainer = document.createElement('div');
15713
+ dotsContainer.className = `dots ${this.dotsOptions.position} ${this.dotsOptions.size}`;
15714
+ dotsContainer.style.cssText = `--opacity: ${this.dotsOptions.opacity}`;
15715
+ this.slides.forEach((_, index) => {
15716
+ const dot = document.createElement('span');
15717
+ dot.className = `dot ${index === this.currentSlide ? 'active' : ''}`;
15718
+ dot.style.backgroundColor = this.dotsOptions.color;
15719
+ dotsContainer.appendChild(dot);
15720
+ });
15721
+ return dotsContainer;
15722
+ }
15723
+ renderButtons() {
15724
+ const buttonsContainer = document.createElement('div');
15725
+ const buttonsClass = this.buttonsOptions.together
15726
+ ? `buttons-together ${this.buttonsOptions.position}`
15727
+ : 'buttons-separate';
15728
+ buttonsContainer.className = `buttons ${buttonsClass} ${this.buttonsOptions.size}`;
15729
+ buttonsContainer.style.cssText = `--opacity: ${this.buttonsOptions.opacity}`;
15730
+ this.prevButton = this.createButton('prev-button', this.buttonsOptions.prev);
15731
+ this.nextButton = this.createButton('next-button', this.buttonsOptions.next);
15732
+ buttonsContainer.appendChild(this.prevButton);
15733
+ buttonsContainer.appendChild(this.nextButton);
15734
+ return buttonsContainer;
15735
+ }
15736
+ createButton(className, text) {
15737
+ const button = document.createElement('button');
15738
+ button.className = className;
15739
+ button.textContent = text;
15740
+ button.style.color = this.buttonsOptions.textColor;
15741
+ button.style.backgroundColor = this.buttonsOptions.backgroundColor;
15742
+ button.style.borderRadius = this.buttonsOptions.borderRadius;
15743
+ return button;
15744
+ }
15745
+ setupDots() {
15746
+ if (!this.shadowRoot || !this.useDots)
15747
+ return;
15748
+ this.dotElements = Array.from(this.shadowRoot.querySelectorAll('.dot'));
15749
+ this.dotElements.forEach((dot, index) => {
15750
+ dot.addEventListener('click', () => {
15751
+ this.goToSlide(index);
15752
+ this.resetAutoplay();
15753
+ });
15754
+ });
15755
+ }
15756
+ setupButtons() {
15757
+ var _a, _b;
15758
+ if (!this.useButtons)
15759
+ return;
15760
+ (_a = this.prevButton) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => {
15761
+ this.prevSlide();
15762
+ this.resetAutoplay();
15763
+ });
15764
+ (_b = this.nextButton) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => {
15765
+ this.nextSlide();
15766
+ this.resetAutoplay();
15767
+ });
15768
+ }
15769
+ nextSlide() {
15770
+ this.goToSlide((this.currentSlide + 1) % this.slides.length);
15771
+ }
15772
+ prevSlide() {
15773
+ this.goToSlide((this.currentSlide - 1 + this.slides.length) % this.slides.length);
15774
+ }
15775
+ goToSlide(index) {
15776
+ this.currentSlide = index;
15777
+ this.updateCarousel();
15778
+ }
15779
+ updateCarousel() {
15780
+ if (!this.slidesContainer)
15781
+ return;
15782
+ const slides = Array.from(this.slidesContainer.children);
15783
+ slides.forEach((slide, index) => {
15784
+ slide.classList.toggle('active', index === this.currentSlide);
15785
+ });
15786
+ this.updateDots();
15787
+ }
15788
+ updateDots() {
15789
+ if (!this.useDots)
15790
+ return;
15791
+ this.dotElements.forEach((dot, index) => {
15792
+ const isActive = index === this.currentSlide;
15793
+ dot.classList.toggle('active', isActive);
15794
+ dot.style.backgroundColor = isActive
15795
+ ? this.dotsOptions.activeColor
15796
+ : this.dotsOptions.color;
15797
+ });
15798
+ }
15799
+ startAutoplay() {
15800
+ this.autoplayInterval = window.setInterval(() => this.nextSlide(), this.interval);
15801
+ }
15802
+ stopAutoplay() {
15803
+ if (this.autoplayInterval !== null) {
15804
+ window.clearInterval(this.autoplayInterval);
15805
+ this.autoplayInterval = null;
15806
+ }
15807
+ }
15808
+ resetAutoplay() {
15809
+ if (this.autoplay) {
15810
+ this.stopAutoplay();
15811
+ this.startAutoplay();
15812
+ }
15813
+ }
15814
+ validateOptions() {
15815
+ this.validatePosition(this.dotsOptions.position, 'dotsPosition', 'bottom-center');
15816
+ this.validateButtonsPosition();
15817
+ }
15818
+ validatePosition(position, optionName, defaultValue) {
15819
+ if (!CustomCarouselElement.validPositions.includes(position)) {
15820
+ console.warn(`Invalid ${optionName}: ${position}. Defaulting to '${defaultValue}'.`);
15821
+ if (optionName === 'dotsPosition') {
15822
+ this.dotsOptions.position = defaultValue;
15823
+ }
15824
+ else if (optionName === 'buttonsPosition') {
15825
+ this.buttonsOptions.position = defaultValue;
15826
+ }
15827
+ }
15828
+ }
15829
+ validateButtonsPosition() {
15830
+ if (this.useButtons) {
15831
+ if (this.buttonsOptions.together) {
15832
+ this.validatePosition(this.buttonsOptions.position, 'buttonsPosition', 'bottom-center');
15833
+ }
15834
+ else if (this.buttonsOptions.position !== 'middle-sides') {
15835
+ console.warn(`Invalid buttonsPosition: ${this.buttonsOptions.position}. When buttons are not together, only 'middle-sides' is allowed. Defaulting to 'middle-sides'.`);
15836
+ this.buttonsOptions.position = 'middle-sides';
15837
+ }
15838
+ }
15839
+ }
15840
+ }
15841
+ CustomCarouselElement.defaultInterval = 5000;
15842
+ CustomCarouselElement.validPositions = [
15843
+ 'top-left',
15844
+ 'top-center',
15845
+ 'top-right',
15846
+ 'bottom-left',
15847
+ 'bottom-center',
15848
+ 'bottom-right',
15849
+ 'middle-left',
15850
+ 'middle-right',
15851
+ 'middle-sides',
15852
+ ];
15853
+ CarouselElement = CustomCarouselElement;
15854
+ }
15855
+
15856
+ let SpotElement;
15857
+ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') {
15858
+ class CustomSpotElement extends HTMLElement {
15859
+ constructor() {
15860
+ super();
15861
+ this.originalFontSizes = new Map();
15862
+ this.attachShadow({ mode: 'open' });
15863
+ }
15864
+ connectedCallback() {
15865
+ this.setupResizeObserver();
15866
+ this.render();
15867
+ }
15868
+ disconnectedCallback() {
15869
+ var _a;
15870
+ this.removeEventListener('spotSizeChanged', this.handleSpotSizeChanged);
15871
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
15872
+ }
15873
+ /**
15874
+ * Setup observers for the spot element
15875
+ * #########################################################
15876
+ */
15877
+ setupResizeObserver() {
15878
+ if (this.data && !this.data.fluid) {
15879
+ this.resizeObserver = new ResizeObserverService({
15880
+ element: this,
15881
+ maxSize: {
15882
+ width: this.data.width,
15883
+ height: this.data.height,
15884
+ },
15885
+ minScale: this.data.minScale,
15886
+ });
15887
+ this.addEventListener('spotSizeChanged', this.handleSpotSizeChanged.bind(this));
15888
+ }
15889
+ }
15890
+ /**
15891
+ * Observer additional event handlers
15892
+ * #########################################################
15893
+ */
15894
+ handleSpotSizeChanged(event) {
15895
+ var _a, _b, _c;
15896
+ const isRBSpot = (_c = (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.spot) === null || _b === void 0 ? void 0 : _b.startsWith('rb')) !== null && _c !== void 0 ? _c : false;
15897
+ if (!isRBSpot) {
15898
+ // Adjust text elements font size based on the scale factor
15899
+ this.adjustFontSize(event.detail.scale);
15900
+ }
15901
+ }
15902
+ adjustFontSize(elementScale) {
15903
+ var _a;
15904
+ const scaleFactor = calculateScaleFactor(elementScale);
15905
+ // Find all text elements within the shadow root
15906
+ const elements = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('h1, h2, h3, h4, p, span');
15907
+ elements === null || elements === void 0 ? void 0 : elements.forEach((element) => {
15908
+ if (element instanceof HTMLElement) {
15909
+ if (!this.originalFontSizes.has(element)) {
15910
+ const originalSize = parseFloat(window.getComputedStyle(element).fontSize);
15911
+ this.originalFontSizes.set(element, originalSize);
15912
+ }
15913
+ const originalSize = this.originalFontSizes.get(element);
15914
+ const newFontSize = originalSize * scaleFactor;
15915
+ element.style.fontSize = `${newFontSize}px`;
15916
+ }
15917
+ });
15918
+ }
15919
+ /**
15920
+ * Spot element rendering
15921
+ * #########################################################
15922
+ */
15923
+ render() {
15924
+ if (!this.shadowRoot || !this.data || !this.content)
15925
+ return;
15926
+ const style = this.getTemplateStyle(this.data.width, this.data.height);
15927
+ if (this.content instanceof HTMLElement) {
15928
+ this.shadowRoot.replaceChildren(style, this.content);
15929
+ }
15930
+ }
15931
+ getTemplateStyle(width, height) {
15932
+ const style = document.createElement('style');
15933
+ style.textContent = `
15934
+ :host {
15935
+ display: block;
15936
+ position: relative;
15937
+ box-sizing: border-box;
15938
+ overflow: hidden;
15939
+ width: ${this.data.fluid ? '100%' : `${width}px`};
15940
+ height: ${this.data.fluid ? '100%' : `${height}px`};
15941
+ }
15942
+ `;
15943
+ return style;
15944
+ }
15945
+ }
15946
+ SpotElement = CustomSpotElement;
15947
+ }
15948
+
15949
+ class ElementService {
15950
+ static getInstance() {
15951
+ return SingletonManager.getInstance('ElementService', () => new ElementService());
15952
+ }
15953
+ /**
15954
+ * Creates the html element based on the provided data, content and configs using shadow dom.
15955
+ *
15956
+ * This method is only available in browser environments.
15957
+ *
15958
+ * @param {ICreateSpotElementParams} params - The parameters to create the final element.
15959
+ *
15960
+ * @return {HTMLElement | null} - The html element or null if the browser environment is not available.
15961
+ */
15962
+ createSpotElement({ content, config }) {
15963
+ var _a, _b;
15964
+ if (!this.ensureBrowserEnvironmentAndDefineElement()) {
15965
+ return null;
15966
+ }
15967
+ const spot = document.createElement(SPOT_ELEMENT_TAG);
15968
+ spot.setAttribute('type', (_a = config === null || config === void 0 ? void 0 : config.spot) !== null && _a !== void 0 ? _a : '');
15969
+ spot.data = {
15970
+ spot: config === null || config === void 0 ? void 0 : config.spot,
15971
+ fluid: (_b = config === null || config === void 0 ? void 0 : config.fluid) !== null && _b !== void 0 ? _b : false,
15972
+ ...config,
15973
+ };
15974
+ spot.content = content;
15975
+ return spot;
15976
+ }
15977
+ /**
15978
+ * Creates the carousel html element based on the provided slides and configs using shadow dom.
15979
+ *
15980
+ * This method is only available in browser environments.
15981
+ *
15982
+ * @param {ICreateCarouselElementParams} params - The parameters to create the final element.
15983
+ *
15984
+ * @return {HTMLElement | null} - The html element or null if the browser environment is not available.
15985
+ */
15986
+ createCarouselElement({ slides, config, }) {
15987
+ if (!this.ensureBrowserEnvironmentAndDefineElement()) {
15988
+ return null;
15989
+ }
15990
+ const carousel = document.createElement(CAROUSEL_ELEMENT_TAG);
15991
+ carousel.data = {
15992
+ spot: config === null || config === void 0 ? void 0 : config.spot,
15993
+ fluid: false,
15994
+ ...config,
15995
+ };
15996
+ carousel.slides = slides;
15997
+ return carousel;
15998
+ }
15999
+ /**
16000
+ * Overrides the spot colors with the provided colors.
16001
+ *
16002
+ * @param {ISpot} spot - The spot data.
16003
+ * @param {ISpotColors} colors - The colors to override.
16004
+ *
16005
+ * @return {ISpot} - The spot data with the colors overridden.
16006
+ */
16007
+ overrideSpotColors(spot, colors) {
16008
+ if (!colors)
16009
+ return spot;
16010
+ const { textColor, backgroundColor, ctaTextColor, ctaBorderColor } = colors;
16011
+ return {
16012
+ ...spot,
16013
+ textColor: textColor !== null && textColor !== void 0 ? textColor : spot.textColor,
16014
+ backgroundColor: backgroundColor !== null && backgroundColor !== void 0 ? backgroundColor : spot.backgroundColor,
16015
+ ctaTextColor: ctaTextColor !== null && ctaTextColor !== void 0 ? ctaTextColor : spot.ctaTextColor,
16016
+ ctaBorderColor: ctaBorderColor !== null && ctaBorderColor !== void 0 ? ctaBorderColor : spot.ctaBorderColor,
16017
+ };
16018
+ }
16019
+ /**
16020
+ * @returns {boolean} - True if the browser environment is available and the element is defined.
16021
+ */
16022
+ ensureBrowserEnvironmentAndDefineElement() {
16023
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
16024
+ console.warn('LiquidCommerce Rmn Sdk: Methods which create elements are only available in browser environments.');
16025
+ return false;
16026
+ }
16027
+ if (!window.customElements.get(SPOT_ELEMENT_TAG)) {
16028
+ window.customElements.define(SPOT_ELEMENT_TAG, SpotElement);
16029
+ }
16030
+ if (!window.customElements.get(CAROUSEL_ELEMENT_TAG)) {
16031
+ window.customElements.define(CAROUSEL_ELEMENT_TAG, CarouselElement);
16032
+ }
16033
+ return true;
16034
+ }
16035
+ }
16036
+
16037
+ class UniqueIdGenerator {
16038
+ /**
16039
+ * Initialize the generator with a node ID
16040
+ * @param nodeId Number between 0-1023 to identify this instance
16041
+ */
16042
+ static initialize(nodeId = Math.floor(Math.random() * 1024)) {
16043
+ if (nodeId < 0 || nodeId >= 1 << this.nodeBits) {
16044
+ throw new Error(`Node ID must be between 0 and ${(1 << this.nodeBits) - 1}`);
16045
+ }
16046
+ this.nodeId = nodeId;
16047
+ }
16048
+ /**
16049
+ * Convert a number to base32 string with specified length
16050
+ */
16051
+ static toBase32(num, length) {
16052
+ let result = '';
16053
+ while (num > 0) {
16054
+ result = this.base32Chars[Number(num % BigInt(32))] + result;
16055
+ // @ts-expect-error - TS doesn't support bigint division
16056
+ num = num / 32n;
16057
+ }
16058
+ return result.padStart(length, '0');
16059
+ }
16060
+ /**
16061
+ * Generate a cryptographically secure random number
16062
+ */
16063
+ static getSecureRandom() {
16064
+ if (typeof crypto !== 'undefined') {
16065
+ const buffer = new Uint32Array(1);
16066
+ crypto.getRandomValues(buffer);
16067
+ return buffer[0];
16068
+ }
16069
+ return Math.floor(Math.random() * 0xffffffff);
16070
+ }
16071
+ /**
16072
+ * Wait until next millisecond
16073
+ */
16074
+ static waitNextMillis(lastTimestamp) {
16075
+ let timestamp = Date.now();
16076
+ while (timestamp <= lastTimestamp) {
16077
+ timestamp = Date.now();
16078
+ }
16079
+ return timestamp;
16080
+ }
16081
+ /**
16082
+ * Generates a highly unique ID with the following format:
16083
+ * TTTTTTTTTTCCCCNNNNNRRRR
16084
+ * T: Timestamp (10 chars)
16085
+ * C: Counter (4 chars)
16086
+ * N: Node ID (5 chars)
16087
+ * R: Random (4 chars)
16088
+ *
16089
+ * Total length: 23 characters, always uppercase alphanumeric
16090
+ */
16091
+ static generate() {
16092
+ if (this.nodeId === undefined) {
16093
+ this.initialize();
16094
+ }
16095
+ let timestamp = Date.now() - this.epoch;
16096
+ // Handle clock moving backwards or same millisecond
16097
+ if (timestamp < this.lastTimestamp) {
16098
+ throw new Error('Clock moved backwards. Refusing to generate ID.');
16099
+ }
16100
+ if (timestamp === this.lastTimestamp) {
16101
+ this.sequence = (this.sequence + 1) & ((1 << this.sequenceBits) - 1);
16102
+ if (this.sequence === 0) {
16103
+ timestamp = this.waitNextMillis(this.lastTimestamp);
16104
+ }
16105
+ }
16106
+ else {
16107
+ this.sequence = 0;
16108
+ }
16109
+ this.lastTimestamp = timestamp;
16110
+ // Generate random component
16111
+ const random = this.getSecureRandom() & 0xffff; // 16 bits of randomness
16112
+ // Combine all components into a BigInt
16113
+ // const id =
16114
+ // (BigInt(timestamp) << BigInt(this.nodeBits + this.sequenceBits + 16)) |
16115
+ // (BigInt(this.nodeId) << BigInt(this.sequenceBits + 16)) |
16116
+ // (BigInt(this.sequence) << BigInt(16)) |
16117
+ // BigInt(random);
16118
+ // Convert to base32 representation
16119
+ const timeComponent = this.toBase32(BigInt(timestamp), 10);
16120
+ const counterComponent = this.toBase32(BigInt(this.sequence), 4);
16121
+ const nodeComponent = this.toBase32(BigInt(this.nodeId), 5);
16122
+ const randomComponent = this.toBase32(BigInt(random), 4);
16123
+ return `${timeComponent}${counterComponent}${nodeComponent}${randomComponent}`;
16124
+ }
16125
+ /**
16126
+ * Validates if a string matches the expected ID format
16127
+ */
16128
+ static isValid(id) {
16129
+ if (!/^[0-9A-HJ-NP-Z]{23}$/.test(id))
16130
+ return false;
16131
+ try {
16132
+ const timeComponent = id.slice(0, 10);
16133
+ const timestamp = this.decodeBase32(timeComponent);
16134
+ const now = Date.now() - this.epoch;
16135
+ return timestamp >= 0 && timestamp <= now;
16136
+ }
16137
+ catch (_a) {
16138
+ return false;
16139
+ }
16140
+ }
16141
+ /**
16142
+ * Decode base32 string to number
16143
+ */
16144
+ static decodeBase32(str) {
16145
+ let result = 0;
16146
+ for (const char of str) {
16147
+ result = result * 32 + this.base32Chars.indexOf(char);
16148
+ }
16149
+ return result;
16150
+ }
16151
+ /**
16152
+ * Extract timestamp from ID
16153
+ */
16154
+ static getTimestamp(id) {
16155
+ if (!this.isValid(id))
16156
+ throw new Error('Invalid ID format');
16157
+ const timeComponent = id.slice(0, 10);
16158
+ const timestamp = this.decodeBase32(timeComponent);
16159
+ return new Date(timestamp + this.epoch);
16160
+ }
16161
+ }
16162
+ // Constants for bit manipulation
16163
+ UniqueIdGenerator.epoch = 1577836800000; // 2020-01-01 as epoch
16164
+ UniqueIdGenerator.nodeBits = 10;
16165
+ UniqueIdGenerator.sequenceBits = 12;
16166
+ // Instance variables
16167
+ UniqueIdGenerator.lastTimestamp = -1;
16168
+ UniqueIdGenerator.sequence = 0;
16169
+ // Character set for base32 encoding (excluding similar looking characters)
16170
+ UniqueIdGenerator.base32Chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
16171
+
16172
+ function convertHexToRgba(hex, opacity = 1) {
16173
+ // Remove # if present
16174
+ const cleanHex = hex.replace('#', '');
16175
+ // Convert hex to RGB
16176
+ const r = parseInt(cleanHex.substring(0, 2), 16);
16177
+ const g = parseInt(cleanHex.substring(2, 4), 16);
16178
+ const b = parseInt(cleanHex.substring(4, 6), 16);
16179
+ // Return rgba string
16180
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
16181
+ }
16182
+ function generateGradientColor(overlay, fallback = '') {
16183
+ if (!overlay) {
16184
+ return fallback;
16185
+ }
16186
+ const OVERLAY_SIZE = {
16187
+ small: 10,
16188
+ base: 30,
16189
+ large: 50,
16190
+ };
16191
+ const OVERLAY_OPACITY = {
16192
+ light: 0.1,
16193
+ medium: 0.4,
16194
+ dark: 0.6,
16195
+ };
16196
+ const { size, opacity, color } = overlay;
16197
+ const goTo = OVERLAY_SIZE[size];
16198
+ const overlayOpacity = OVERLAY_OPACITY[opacity];
16199
+ const fullColor = convertHexToRgba(color, 1);
16200
+ const transparentColor = convertHexToRgba(color, 0);
16201
+ const gradientColor = convertHexToRgba(color, overlayOpacity);
16202
+ return `${fullColor} 0%, ${gradientColor} ${goTo}%, ${transparentColor} 100%`;
16203
+ }
16204
+ function spotHtmlStringToElement(htmlString) {
16205
+ const spot = document.createElement('div');
16206
+ spot.className = 'spot';
16207
+ spot.innerHTML = htmlString;
16208
+ Object.assign(spot.style, {
16209
+ position: 'relative',
16210
+ display: 'block',
16211
+ width: '100%',
16212
+ height: '100%',
16213
+ margin: '0',
16214
+ padding: '0',
16215
+ });
16216
+ return spot;
16217
+ }
16218
+
16219
+ const STYLES$i = ({ primaryImage, secondaryImage }) => `
16220
+ <style>
16221
+ .content {
16222
+ width: 100%;
16223
+ height: 100%;
16224
+ display: flex;
16225
+ flex-direction: row;
16226
+ background-color: #FFFFFF;
16227
+ gap: 0.5cqw;
16228
+ cursor: pointer;
16229
+ color: inherit;
16230
+ }
16231
+ .big-image {
16232
+ width: 65%;
16233
+ height: 100%;
16234
+ background-image: url("${primaryImage}");
16235
+ background-position: center;
16236
+ background-repeat: no-repeat;
16237
+ background-size: cover;
16238
+ }
16239
+ .main {
16240
+ width: 35%;
16241
+ height: 100%;
16242
+ display: flex;
16243
+ flex-direction: column;
16244
+ gap: 0.5cqw;
16245
+ }
16246
+ .small-image {
16247
+ width: 100%;
16248
+ height: 50%;
16249
+ background-image: url("${secondaryImage}");
16250
+ background-position: center;
16251
+ background-repeat: no-repeat;
16252
+ background-size: cover;
16253
+ }
16254
+ .text {
16255
+ background: #E8E6DE;
16256
+ text-align: center;
16257
+ display: flex;
16258
+ flex-direction: column;
16259
+ justify-content: center;
16260
+ align-items: center;
16261
+ height: 50%;
16262
+ width: 100%;
16263
+ padding: 5% 10%;
16264
+ box-sizing: border-box;
16265
+ font-family: "Source Sans 3", sans-serif;
16266
+ }
16267
+ .header {
16268
+ font-size: 2.2cqw;
16269
+ color: #000000;
16270
+ font-style: normal;
16271
+ font-weight: 400;
16272
+ font-family: "Cormorant", serif;
16273
+ margin: 0;
16274
+ }
16275
+ .description {
16276
+ font-size: 1.2cqw;
16277
+ color: #000000;
16278
+ font-style: normal;
16279
+ font-weight: 400;
16280
+ margin: 1cqh 0;
16281
+ }
16282
+ .button {
16283
+ font-size: 1cqw;
16284
+ color: #000000;
16285
+ background-color: transparent;
16286
+ font-style: normal;
16287
+ font-weight: 700;
16288
+ text-decoration: underline;
16289
+ text-transform: uppercase;
16290
+ border: none;
16291
+ cursor: pointer;
16292
+ padding: 0;
16293
+ transition: opacity 0.3s ease;
16294
+ }
16295
+ .content:hover .button {
16296
+ opacity: 0.7;
16297
+ }
16298
+ @container (max-width: 600px) {
16299
+ .header {
16300
+ font-size: 3cqw;
16301
+ }
16302
+ .description {
16303
+ font-size: 1.6cqw;
16304
+ margin: 0;
16305
+ }
16306
+ .button {
16307
+ font-size: 1.4cqw;
16308
+ }
16309
+ }
16310
+ </style>
16311
+ `;
16312
+ function billboardV1Template(spot) {
16313
+ return `
16314
+ ${GFONT_PRECONNECT}
16315
+ ${GFONT_SOURCE_SANS_3}
16316
+ ${GFONT_CORMORANT}
15303
16317
  ${STYLES$i(spot)}
15304
16318
  <div class="content">
15305
16319
  <div class="big-image"></div>
@@ -16019,185 +17033,204 @@ function wideSkyscraperV1Template(spot) {
16019
17033
  `;
16020
17034
  }
16021
17035
 
16022
- const STYLES$7 = ({ primaryImage, mobilePrimaryImage = primaryImage }) => `
16023
- <style>
16024
- :host {
16025
- min-width: 320px;
16026
- min-height: 223px;
16027
- }
16028
- .content {
16029
- display: block;
16030
- width: 100%;
16031
- height: 100%;
16032
- background-image: url("${mobilePrimaryImage}");
16033
- background-size: cover;
16034
- background-repeat: no-repeat;
16035
- background-position: center;
16036
- cursor: pointer;
16037
- }
16038
- @media (min-width: 640px) {
16039
- .content {
16040
- background-image: url("${primaryImage}");
16041
- }
17036
+ const STYLES$7 = ({ primaryImage, mobilePrimaryImage = primaryImage }, { prefix }) => `
17037
+ <style>
17038
+ .${prefix} {
17039
+ display: block;
17040
+ width: 100%;
17041
+ height: 100%;
17042
+ background-image: url("${mobilePrimaryImage}");
17043
+ background-size: cover;
17044
+ background-repeat: no-repeat;
17045
+ background-position: center;
17046
+ cursor: pointer;
17047
+ container-type: inline-size;
17048
+ }
17049
+
17050
+ @container (min-width: 640px) {
17051
+ .${prefix} {
17052
+ background-image: url("${primaryImage}");
16042
17053
  }
16043
- </style>
17054
+ }
17055
+ </style>
16044
17056
  `;
16045
- function rbCollectionBannerWithoutTextBlockTemplate(spot) {
17057
+ function rbCollectionBannerWithoutTextBlockTemplate(spot, config) {
17058
+ const { prefix = '' } = config;
16046
17059
  return `
16047
- ${STYLES$7(spot)}
16048
- <div class="content"></div>
17060
+ ${STYLES$7(spot, config)}
17061
+ <div class="${prefix}"></div>
16049
17062
  `;
16050
17063
  }
16051
17064
 
16052
- const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderColor = ctaTextColor, primaryImage, mobilePrimaryImage = primaryImage, }) => `
16053
- <style>
16054
- :host {
16055
- min-width: 320px;
16056
- min-height: 388px;
16057
- }
16058
- .content {
16059
- display: flex;
16060
- flex-direction: column;
16061
- justify-content: flex-end;
16062
- align-items: flex-start;
16063
- width: 100%;
16064
- height: 100%;
16065
- background-image: url("${mobilePrimaryImage}");
16066
- background-size: cover;
16067
- background-repeat: no-repeat;
16068
- background-position: center;
16069
- padding: 3%;
16070
- box-sizing: border-box;
16071
- color: ${textColor};
16072
- cursor: pointer;
16073
- }
16074
- .text {
16075
- display: flex;
16076
- flex-direction: column;
16077
- justify-content: flex-start;
16078
- align-items: flex-start;
16079
- width: 100%;
16080
- height: fit-content;
16081
- gap: 5px;
16082
- }
16083
- .header {
16084
- font-size: 18px;
16085
- margin: 0;
16086
- font-family: "Cormorant";
16087
- font-style: normal;
16088
- font-weight: 300;
16089
- line-height: normal;
16090
- }
16091
- .description {
16092
- font-size: 10px;
16093
- font-family: "Source Sans 3", system-ui;
16094
- font-style: normal;
16095
- font-weight: 400;
16096
- line-height: 20px;
16097
- margin: 0;
16098
- }
16099
- .cta-button {
16100
- font-size: 8px;
16101
- font-family: "Source Sans 3", system-ui;
16102
- font-style: normal;
16103
- font-weight: 400;
16104
- line-height: 18px;
16105
- border-radius: 5px;
16106
- border: 0.5px solid ${ctaBorderColor};
16107
- display: inline-block;
16108
- padding: 7px 20px;
16109
- color: ${ctaTextColor};
16110
- transition: background-color 0.3s ease;
16111
- }
16112
- .content:hover .cta-button {
16113
- background-color: ${ctaBorderColor.length === 7 ? `${ctaBorderColor}15` : `${ctaBorderColor}`};
16114
- }
16115
- @media (min-width: 640px) {
16116
- .content {
16117
- background-image: url("${primaryImage}");
16118
- }
16119
- }
16120
- @media (min-width: 768px) {
16121
- .primary-image {
16122
- width: 66.66666%;
16123
- height: 100%;
16124
- }
16125
- .main {
17065
+ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderColor = ctaTextColor, primaryImage, mobilePrimaryImage = primaryImage, }, { prefix, overlay }) => {
17066
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 40%');
17067
+ return `
17068
+ <style>
17069
+ .${prefix} {
17070
+ display: flex;
16126
17071
  flex-direction: column;
17072
+ justify-content: flex-end;
17073
+ align-items: flex-start;
17074
+ width: 100%;
16127
17075
  height: 100%;
16128
- width: 33.33333%;
17076
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
17077
+ background-size: cover;
17078
+ background-repeat: no-repeat;
17079
+ background-position: center;
17080
+ padding: 3%;
17081
+ box-sizing: border-box;
17082
+ color: ${textColor};
17083
+ cursor: pointer;
17084
+ container-type: inline-size;
16129
17085
  }
16130
- .secondary-image {
17086
+
17087
+ .${prefix}__text {
17088
+ display: flex;
17089
+ flex-direction: column;
17090
+ justify-content: flex-start;
17091
+ align-items: flex-start;
16131
17092
  width: 100%;
16132
- height: 50%;
16133
- }
16134
- .header {
16135
- font-size: 22px;
17093
+ height: fit-content;
17094
+ gap: 8px;
16136
17095
  }
16137
- .description {
16138
- font-size: 12px;
17096
+
17097
+ .${prefix}__header {
17098
+ font-size: 24px;
17099
+ margin: 0;
17100
+ font-family: "Cormorant";
17101
+ font-style: normal;
17102
+ font-weight: 300;
17103
+ line-height: normal;
16139
17104
  }
16140
- .cta-button {
16141
- font-size: 12px;
17105
+
17106
+ .${prefix}__description {
17107
+ font-size: 10px;
17108
+ font-family: "Source Sans 3", system-ui;
17109
+ font-style: normal;
17110
+ font-weight: 400;
17111
+ line-height: 20px;
17112
+ margin: 0;
16142
17113
  }
16143
- }
16144
- @media (min-width: 1024px) {
16145
- .header {
16146
- font-size: 24px;
17114
+
17115
+ .${prefix}__cta-button {
17116
+ font-size: 8px;
17117
+ font-family: "Source Sans 3", system-ui;
17118
+ font-style: normal;
17119
+ font-weight: 400;
17120
+ line-height: 18px;
17121
+ border-radius: 5px;
17122
+ border: 0.5px solid ${ctaBorderColor};
17123
+ display: inline-block;
17124
+ padding: 7px 20px;
17125
+ color: ${ctaTextColor};
17126
+ transition: background-color 0.3s ease;
16147
17127
  }
16148
- .description {
16149
- font-size: 13px;
17128
+
17129
+ .${prefix}:hover .cta-button {
17130
+ background-color: ${ctaBorderColor.length === 7 ? `${ctaBorderColor}15` : `${ctaBorderColor}`};
16150
17131
  }
16151
- .cta-button {
16152
- font-size: 13px;
17132
+
17133
+ @container (min-width: 640px) {
17134
+ .${prefix} {
17135
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
17136
+ }
16153
17137
  }
16154
- }
16155
- @media (min-width: 1280px) {
16156
- .header {
16157
- font-size: 28px;
17138
+
17139
+ @container (min-width: 768px) {
17140
+ .${prefix}__primary-image {
17141
+ width: 66.66666%;
17142
+ height: 100%;
17143
+ }
17144
+
17145
+ .${prefix}__main {
17146
+ flex-direction: column;
17147
+ height: 100%;
17148
+ width: 33.33333%;
17149
+ }
17150
+
17151
+ .${prefix}__secondary-image {
17152
+ width: 100%;
17153
+ height: 50%;
17154
+ }
17155
+
17156
+ .${prefix}__description {
17157
+ font-size: 12px;
17158
+ }
17159
+
17160
+ .${prefix}__cta-button {
17161
+ font-size: 12px;
17162
+ }
16158
17163
  }
16159
- .description {
16160
- font-size: 14px;
17164
+
17165
+ @container (min-width: 1024px) {
17166
+ .${prefix}__header {
17167
+ font-size: 26px;
17168
+ }
17169
+
17170
+ .${prefix}__description {
17171
+ font-size: 13px;
17172
+ }
17173
+
17174
+ .${prefix}__cta-button {
17175
+ font-size: 13px;
17176
+ }
16161
17177
  }
16162
- .cta-button {
16163
- font-size: 14px;
17178
+
17179
+ @container (min-width: 1280px) {
17180
+ .${prefix}__header {
17181
+ font-size: 28px;
17182
+ }
17183
+
17184
+ .${prefix}__description {
17185
+ font-size: 14px;
17186
+ }
17187
+
17188
+ .${prefix}__cta-button {
17189
+ font-size: 14px;
17190
+ }
16164
17191
  }
16165
- }
16166
- </style>
16167
- `;
16168
- function rbHomepageHeroFullImageTemplate(spot) {
17192
+ </style>
17193
+ `;
17194
+ };
17195
+ function rbHomepageHeroFullImageTemplate(spot, config) {
17196
+ const { prefix = '' } = config;
16169
17197
  return `
16170
17198
  ${GFONT_PRECONNECT}
16171
17199
  ${GFONT_SOURCE_SANS_3}
16172
17200
  ${GFONT_CORMORANT}
16173
- ${STYLES$6(spot)}
16174
- <div class="content">
16175
- <div class="text">
16176
- ${spot.header ? `<h2 class="header">${spot.header}</h2>` : ''}
16177
- ${spot.description ? `<p class="description">${spot.description}</p>` : ''}
16178
- ${spot.ctaText ? `<span class="cta-button">${spot.ctaText}</span>` : ''}
16179
- </div>
17201
+ ${STYLES$6(spot, config)}
17202
+ <div class="${prefix}">
17203
+ <div class="${prefix}__text">
17204
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17205
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17206
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
17207
+ </div>
16180
17208
  </div>
16181
17209
  `;
16182
17210
  }
16183
17211
 
16184
- const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextColor = textColor, primaryImage, mobilePrimaryImage = primaryImage, secondaryImage, mobileSecondaryImage = secondaryImage, }) => `
16185
- <style>
16186
- :host {
16187
- min-width: 320px;
16188
- min-height: 388px;
17212
+ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextColor = textColor, primaryImage, mobilePrimaryImage = primaryImage, secondaryImage, mobileSecondaryImage = secondaryImage, }, { prefix }) => `
17213
+ <style>
17214
+ .${prefix} {
17215
+ width: 100%;
17216
+ height: 100%;
17217
+ display: block;
17218
+ position: relative;
17219
+ container-type: inline-size;
16189
17220
  }
16190
- .content {
17221
+
17222
+ .${prefix}__content {
16191
17223
  width: 100%;
16192
17224
  height: 100%;
16193
17225
  display: flex;
16194
- flex-direction: column;
17226
+ flex-direction: column;
16195
17227
  background-color: transparent;
16196
- gap: 5px;
17228
+ gap: 6px;
16197
17229
  color: inherit;
16198
17230
  cursor: pointer;
16199
17231
  }
16200
- .primary-image {
17232
+
17233
+ .${prefix}__primary-image {
16201
17234
  width: 100%;
16202
17235
  height: 60%;
16203
17236
  background-image: url("${mobilePrimaryImage}");
@@ -16205,14 +17238,16 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16205
17238
  background-repeat: no-repeat;
16206
17239
  background-size: cover;
16207
17240
  }
16208
- .main {
17241
+
17242
+ .${prefix}__main {
16209
17243
  width: 100%;
16210
17244
  height: 40%;
16211
17245
  display: flex;
16212
17246
  flex-direction: row;
16213
- gap: 5px;
17247
+ gap: 6px;
16214
17248
  }
16215
- .secondary-image {
17249
+
17250
+ .${prefix}__secondary-image {
16216
17251
  width: 50%;
16217
17252
  height: 100%;
16218
17253
  background-image: url("${mobileSecondaryImage}");
@@ -16220,7 +17255,8 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16220
17255
  background-repeat: no-repeat;
16221
17256
  background-size: cover;
16222
17257
  }
16223
- .text {
17258
+
17259
+ .${prefix}__text {
16224
17260
  color: ${textColor};
16225
17261
  background-color: ${backgroundColor};
16226
17262
  text-align: center;
@@ -16230,11 +17266,12 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16230
17266
  align-items: center;
16231
17267
  width: 50%;
16232
17268
  height: 100%;
16233
- gap: 5px;
17269
+ gap: 10px;
16234
17270
  padding: 0 10px;
16235
17271
  box-sizing: border-box;
16236
17272
  }
16237
- .header {
17273
+
17274
+ .${prefix}__header {
16238
17275
  color: inherit;
16239
17276
  margin: 0;
16240
17277
  font-size: 18px;
@@ -16243,7 +17280,8 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16243
17280
  font-weight: 700;
16244
17281
  line-height: normal;
16245
17282
  }
16246
- .description {
17283
+
17284
+ .${prefix}__description {
16247
17285
  color: inherit;
16248
17286
  margin: 0;
16249
17287
  font-size: 10px;
@@ -16251,7 +17289,8 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16251
17289
  font-style: normal;
16252
17290
  font-weight: 400;
16253
17291
  }
16254
- .cta-button {
17292
+
17293
+ .${prefix}__cta-button {
16255
17294
  color: ${ctaTextColor};
16256
17295
  background-color: transparent;
16257
17296
  font-size: 8px;
@@ -16266,108 +17305,120 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16266
17305
  padding: 0;
16267
17306
  transition: opacity 0.3s ease;
16268
17307
  }
16269
- .content:hover .cta-button {
17308
+
17309
+ .${prefix}__content:hover .${prefix}__cta-button {
16270
17310
  opacity: 0.8;
16271
17311
  }
16272
- @media (min-width: 640px) {
16273
- .primary-image {
17312
+
17313
+ @container (min-width: 640px) {
17314
+ .${prefix}__primary-image {
16274
17315
  background-image: url("${primaryImage}");
16275
17316
  }
16276
- .secondary-image {
17317
+
17318
+ .${prefix}__secondary-image {
16277
17319
  background-image: url("${secondaryImage}");
16278
17320
  }
16279
17321
  }
16280
- @media (min-width: 768px) {
16281
- .content {
17322
+
17323
+ @container (min-width: 768px) {
17324
+ .${prefix}__content {
16282
17325
  flex-direction: row;
16283
17326
  }
16284
- .primary-image {
17327
+
17328
+ .${prefix}__primary-image {
16285
17329
  width: 66.66666%;
16286
17330
  height: 100%;
16287
17331
  }
16288
- .main {
17332
+
17333
+ .${prefix}__main {
16289
17334
  flex-direction: column;
16290
17335
  height: 100%;
16291
17336
  width: 33.33333%;
16292
17337
  }
16293
- .secondary-image {
17338
+
17339
+ .${prefix}__secondary-image {
16294
17340
  width: 100%;
16295
17341
  height: 50%;
16296
17342
  }
16297
- .text {
17343
+
17344
+ .${prefix}__text {
16298
17345
  width: 100%;
16299
17346
  height: 50%;
16300
17347
  }
16301
- .header {
17348
+ .${prefix}__header {
16302
17349
  font-size: 22px;
16303
17350
  }
16304
- .description {
17351
+ .${prefix}__description {
16305
17352
  font-size: 12px;
16306
17353
  }
16307
- .cta-button {
17354
+ .${prefix}__cta-button {
16308
17355
  font-size: 12px;
16309
17356
  }
16310
17357
  }
16311
- @media (min-width: 1024px) {
16312
- .header {
17358
+
17359
+ @container (min-width: 1024px) {
17360
+ .${prefix}__header {
16313
17361
  font-size: 24px;
16314
17362
  }
16315
- .description {
17363
+ .${prefix}__description {
16316
17364
  font-size: 13px;
16317
- }
16318
- .cta-button {
16319
- font-size: 13px;
16320
- }
17365
+ }
17366
+ .${prefix}__cta-button {
17367
+ font-size: 13px;
17368
+ }
16321
17369
  }
16322
- @media (min-width: 1280px) {
16323
- .header {
17370
+
17371
+ @container (min-width: 1280px) {
17372
+ .${prefix}__header {
16324
17373
  font-size: 28px;
16325
17374
  }
16326
- .description {
17375
+ .${prefix}__description {
16327
17376
  font-size: 14px;
16328
17377
  }
16329
- .cta-button {
17378
+ .${prefix}__cta-button {
16330
17379
  font-size: 14px;
16331
17380
  }
16332
17381
  }
16333
- </style>
17382
+ </style>
16334
17383
  `;
16335
- function rbHomepageHeroThreeTileTemplate(spot) {
17384
+ function rbHomepageHeroThreeTileTemplate(spot, config) {
17385
+ const { prefix = '' } = config;
16336
17386
  return `
16337
17387
  ${GFONT_PRECONNECT}
16338
17388
  ${GFONT_SOURCE_SANS_3}
16339
17389
  ${GFONT_CORMORANT}
16340
- ${STYLES$5(spot)}
16341
- <div class="content">
16342
- <div class="primary-image"></div>
16343
- <div class="main">
16344
- <div class="secondary-image"></div>
16345
- <div class="text">
16346
- ${spot.header ? `<h2 class="header">${spot.header}</h2>` : ''}
16347
- ${spot.description ? `<p class="description">${spot.description}</p>` : ''}
16348
- ${spot.ctaText ? `<span class="cta-button">${spot.ctaText}</span>` : ''}
17390
+ ${STYLES$5(spot, config)}
17391
+ <div class="${prefix}">
17392
+ <div class="${prefix}__content">
17393
+ <div class="${prefix}__primary-image"></div>
17394
+ <div class="${prefix}__main">
17395
+ <div class="${prefix}__secondary-image"></div>
17396
+ <div class="${prefix}__text">
17397
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17398
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17399
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
17400
+ </div>
16349
17401
  </div>
16350
17402
  </div>
16351
17403
  </div>
16352
17404
  `;
16353
17405
  }
16354
17406
 
16355
- const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextColor = textColor, primaryImage, mobilePrimaryImage = primaryImage, }) => `
17407
+ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextColor = textColor, primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
16356
17408
  <style>
16357
- :host {
16358
- min-width: 320px;
16359
- min-height: 388px;
16360
- }
16361
- .content {
17409
+ .${prefix} {
16362
17410
  width: 100%;
16363
17411
  height: 100%;
16364
17412
  display: flex;
16365
- flex-direction: column-reverse;
17413
+ flex-direction: column-reverse;
16366
17414
  background-color: transparent;
16367
- gap: 5px;
17415
+ gap: 6px;
16368
17416
  cursor: pointer;
17417
+ container-type: inline-size;
17418
+ position: relative;
16369
17419
  }
16370
- .image {
17420
+
17421
+ .${prefix}__image {
16371
17422
  width: 100%;
16372
17423
  height: 100%;
16373
17424
  background-image: url("${mobilePrimaryImage}");
@@ -16375,7 +17426,8 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16375
17426
  background-repeat: no-repeat;
16376
17427
  background-size: cover;
16377
17428
  }
16378
- .text {
17429
+
17430
+ .${prefix}__text {
16379
17431
  color: ${textColor};
16380
17432
  background-color: ${backgroundColor};
16381
17433
  text-align: center;
@@ -16385,11 +17437,12 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16385
17437
  align-items: center;
16386
17438
  width: 100%;
16387
17439
  height: 100%;
16388
- gap: 5px;
17440
+ gap: 10px;
16389
17441
  padding: 0 10px;
16390
17442
  box-sizing: border-box;
16391
17443
  }
16392
- .header {
17444
+
17445
+ .${prefix}__header {
16393
17446
  font-size: 18px;
16394
17447
  margin: 0;
16395
17448
  color: inherit;
@@ -16398,7 +17451,8 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16398
17451
  font-weight: 700;
16399
17452
  line-height: normal;
16400
17453
  }
16401
- .description {
17454
+
17455
+ .${prefix}__description {
16402
17456
  color: inherit;
16403
17457
  margin: 0;
16404
17458
  font-size: 10px;
@@ -16406,7 +17460,8 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16406
17460
  font-style: normal;
16407
17461
  font-weight: 400;
16408
17462
  }
16409
- .cta-button {
17463
+
17464
+ .${prefix}__cta-button {
16410
17465
  color: ${ctaTextColor};
16411
17466
  background-color: transparent;
16412
17467
  font-size: 8px;
@@ -16421,205 +17476,225 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
16421
17476
  padding: 0;
16422
17477
  transition: opacity 0.3s ease;
16423
17478
  }
16424
- .content:hover .cta-button {
17479
+
17480
+ .${prefix}__content:hover .${prefix}__cta-button {
16425
17481
  opacity: 0.8;
16426
17482
  }
16427
- @media (min-width: 640px) {
16428
- .image {
17483
+
17484
+ @container (min-width: 640px) {
17485
+ .${prefix}__image {
16429
17486
  background-image: url("${primaryImage}");
16430
17487
  }
16431
17488
  }
16432
- @media (min-width: 768px) {
16433
- .content {
17489
+
17490
+ @container (min-width: 768px) {
17491
+ .${prefix} {
16434
17492
  flex-direction: row;
16435
17493
  }
16436
- .image {
17494
+ .${prefix}__image {
16437
17495
  width: 66.66666%;
16438
17496
  height: 100%;
16439
17497
  }
16440
- .text {
16441
- width: 33.333%;
17498
+ .${prefix}__text {
17499
+ width: 33.33333%;
16442
17500
  height: 100%;
16443
17501
  }
16444
- .header {
17502
+ .${prefix}__header {
16445
17503
  font-size: 22px;
16446
17504
  }
16447
- .description {
17505
+ .${prefix}__description {
16448
17506
  font-size: 12px;
16449
17507
  }
16450
- .cta-button {
17508
+ .${prefix}__cta-button {
16451
17509
  font-size: 12px;
16452
17510
  }
16453
17511
  }
16454
- @media (min-width: 1024px) {
16455
- .header {
17512
+
17513
+ @container (min-width: 1024px) {
17514
+ .${prefix}__header {
16456
17515
  font-size: 24px;
16457
17516
  }
16458
- .description {
17517
+ .${prefix}__description {
16459
17518
  font-size: 13px;
16460
- }
16461
- .cta-button {
16462
- font-size: 13px;
16463
- }
17519
+ }
17520
+ .${prefix}__cta-button {
17521
+ font-size: 13px;
17522
+ }
16464
17523
  }
16465
- @media (min-width: 1280px) {
16466
- .header {
17524
+
17525
+ @container (min-width: 1280px) {
17526
+ .${prefix}__header {
16467
17527
  font-size: 28px;
16468
17528
  }
16469
- .description {
17529
+ .${prefix}__description {
16470
17530
  font-size: 14px;
16471
17531
  }
16472
- .cta-button {
17532
+ .${prefix}__cta-button {
16473
17533
  font-size: 14px;
16474
17534
  }
16475
17535
  }
16476
17536
  </style>
16477
17537
  `;
16478
- function rbHomepageHeroTwoTileTemplate(spot) {
17538
+ function rbHomepageHeroTwoTileTemplate(spot, config) {
17539
+ const { prefix = '' } = config;
16479
17540
  return `
16480
17541
  ${GFONT_PRECONNECT}
16481
17542
  ${GFONT_SOURCE_SANS_3}
16482
17543
  ${GFONT_CORMORANT}
16483
- ${STYLES$4(spot)}
16484
- <div class="content">
16485
- <div class="text">
16486
- ${spot.header ? `<h2 class="header">${spot.header}</h2>` : ''}
16487
- ${spot.description ? `<p class="description">${spot.description}</p>` : ''}
16488
- ${spot.ctaText ? `<span class="cta-button">${spot.ctaText}</span>` : ''}
17544
+ ${STYLES$4(spot, config)}
17545
+ <div class="${prefix}">
17546
+ <div class="${prefix}__text">
17547
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17548
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17549
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
16489
17550
  </div>
16490
- <div class="image"></div>
17551
+ <div class="${prefix}__image"></div>
16491
17552
  </div>
16492
17553
  `;
16493
17554
  }
16494
17555
 
16495
- const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderColor = ctaTextColor, primaryImage, mobilePrimaryImage = primaryImage, }) => `
16496
- <style>
16497
- :host {
16498
- min-width: 320px;
16499
- min-height: 334px;
16500
- }
16501
- .content {
16502
- width: 100%;
16503
- height: 100%;
16504
- display: flex;
16505
- flex-direction: column;
16506
- justify-content: flex-end;
16507
- background-image: url("${mobilePrimaryImage}");
16508
- background-size: cover;
16509
- background-repeat: no-repeat;
16510
- background-position: center;
16511
- border-radius: 5px;
16512
- overflow: hidden;
16513
- cursor: pointer;
16514
- color: ${textColor};
16515
- }
16516
- .text {
16517
- padding: 20px;
16518
- width: 70%;
16519
- display: flex;
16520
- flex-direction: column;
16521
- justify-content: center;
16522
- align-items: flex-start;
16523
- gap: 10px;
16524
- }
16525
- .header {
16526
- color: inherit;
16527
- margin: 0;
16528
- font-size: 20px;
16529
- font-family: "Cormorant";
16530
- font-style: normal;
16531
- font-weight: 300;
16532
- line-height: normal;
16533
- }
16534
- .description {
16535
- color: inherit;
16536
- font-size: 12px;
16537
- line-height: 16px;
16538
- font-family: "Source Sans 3", system-ui;
16539
- font-style: normal;
16540
- font-weight: 400;
16541
- margin: 0;
16542
- }
16543
- .cta-button {
16544
- width: fit-content;
16545
- display: inline-block;
16546
- padding: 7px 20px;
16547
- border: 0.5px solid ${ctaBorderColor};
16548
- border-radius: 5px;
16549
- color: ${ctaTextColor};
16550
- font-size: 8px;
16551
- transition: background-color 0.3s ease;
16552
- font-family: "Source Sans 3", system-ui;
16553
- font-style: normal;
16554
- font-weight: 400;
16555
- }
16556
- .content:hover .cta-button {
16557
- background-color: ${ctaBorderColor.length === 7 ? `${ctaBorderColor}15` : `${ctaBorderColor}`};
16558
- }
16559
- @media (min-width: 640px) {
16560
- .content {
16561
- background-image: url("${primaryImage}");
16562
- }
16563
- }
16564
- @media (min-width: 768px) {
16565
- .text {
16566
- padding: 25px;
17556
+ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderColor = ctaTextColor, primaryImage, mobilePrimaryImage = primaryImage, }, { prefix, overlay }) => {
17557
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
17558
+ return `
17559
+ <style>
17560
+ .${prefix} {
17561
+ width: 100%;
17562
+ height: 100%;
17563
+ display: flex;
17564
+ flex-direction: column;
17565
+ justify-content: flex-end;
17566
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
17567
+ background-size: cover;
17568
+ background-repeat: no-repeat;
17569
+ background-position: center;
17570
+ border-radius: 5px;
17571
+ overflow: hidden;
17572
+ cursor: pointer;
17573
+ color: ${textColor};
17574
+ container-type: inline-size;
16567
17575
  }
16568
- .header {
16569
- font-size: 24px;
17576
+
17577
+ .${prefix}__text {
17578
+ padding: 20px;
17579
+ width: 70%;
17580
+ display: flex;
17581
+ flex-direction: column;
17582
+ justify-content: center;
17583
+ align-items: flex-start;
17584
+ gap: 10px;
16570
17585
  }
16571
- .description {
16572
- font-size: 13px;
16573
- line-height: 18px;
17586
+
17587
+ .${prefix}__header {
17588
+ color: inherit;
17589
+ margin: 0;
17590
+ font-size: 20px;
17591
+ font-family: "Cormorant";
17592
+ font-style: normal;
17593
+ font-weight: 300;
17594
+ line-height: normal;
16574
17595
  }
16575
- .cta-button {
17596
+
17597
+ .${prefix}__description {
17598
+ color: inherit;
16576
17599
  font-size: 12px;
17600
+ line-height: 16px;
17601
+ font-family: "Source Sans 3", system-ui;
17602
+ font-style: normal;
17603
+ font-weight: 400;
17604
+ margin: 0;
16577
17605
  }
16578
- }
16579
- @media (min-width: 1024px) {
16580
- .text {
16581
- padding: 30px;
17606
+
17607
+ .${prefix}__cta-button {
17608
+ width: fit-content;
17609
+ display: inline-block;
17610
+ padding: 7px 20px;
17611
+ border: 0.5px solid ${ctaBorderColor};
17612
+ border-radius: 5px;
17613
+ color: ${ctaTextColor};
17614
+ font-size: 8px;
17615
+ transition: background-color 0.3s ease;
17616
+ font-family: "Source Sans 3", system-ui;
17617
+ font-style: normal;
17618
+ font-weight: 400;
16582
17619
  }
16583
- .header {
16584
- font-size: 28px;
17620
+
17621
+ .${prefix}:hover .${prefix}__cta-button {
17622
+ background-color: ${ctaBorderColor.length === 7 ? `${ctaBorderColor}15` : `${ctaBorderColor}`};
17623
+ }
17624
+
17625
+ @container (min-width: 640px) {
17626
+ .${prefix} {
17627
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
17628
+ }
16585
17629
  }
16586
- .description {
16587
- font-size: 14px;
17630
+
17631
+ @container (min-width: 768px) {
17632
+ .${prefix}__text {
17633
+ padding: 25px;
17634
+ }
17635
+
17636
+ .${prefix}__header {
17637
+ font-size: 24px;
17638
+ }
17639
+
17640
+ .${prefix}__description {
17641
+ font-size: 13px;
17642
+ line-height: 18px;
17643
+ }
17644
+
17645
+ .${prefix}__cta-button {
17646
+ font-size: 12px;
17647
+ }
16588
17648
  }
16589
- .cta-button {
16590
- font-size: 13px;
17649
+
17650
+ @container (min-width: 1024px) {
17651
+ .${prefix}__text {
17652
+ padding: 30px;
17653
+ }
17654
+
17655
+ .${prefix}__header {
17656
+ font-size: 28px;
17657
+ }
17658
+
17659
+ .${prefix}__description {
17660
+ font-size: 14px;
17661
+ }
17662
+
17663
+ .${prefix}__cta-button {
17664
+ font-size: 13px;
17665
+ }
16591
17666
  }
16592
- }
16593
- @media (min-width: 1280px) {
16594
- .cta-button {
16595
- font-size: 14px;
17667
+
17668
+ @container (min-width: 1280px) {
17669
+ .${prefix}__cta-button {
17670
+ font-size: 14px;
17671
+ }
16596
17672
  }
16597
- }
16598
- </style>
16599
- `;
16600
- function rbLargeCategoryImageToutTemplate(spot) {
17673
+ </style>
17674
+ `;
17675
+ };
17676
+ function rbLargeCategoryImageToutTemplate(spot, config) {
17677
+ const { prefix = '' } = config;
16601
17678
  return `
16602
17679
  ${GFONT_PRECONNECT}
16603
17680
  ${GFONT_SOURCE_SANS_3}
16604
17681
  ${GFONT_CORMORANT}
16605
- ${STYLES$3(spot)}
16606
- <div class="content">
16607
- <div class="text">
16608
- ${spot.header ? `<h2 class="header">${spot.header}</h2>` : ''}
16609
- ${spot.description ? `<p class="description">${spot.description}</p>` : ''}
16610
- ${spot.ctaText ? `<span class="cta-button">${spot.ctaText}</span>` : ''}
17682
+ ${STYLES$3(spot, config)}
17683
+ <div class="${prefix}">
17684
+ <div class="${prefix}__text">
17685
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17686
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17687
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
16611
17688
  </div>
16612
17689
  </div>
16613
17690
  `;
16614
17691
  }
16615
17692
 
16616
- const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage, }) => `
17693
+ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
17694
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
17695
+ return `
16617
17696
  <style>
16618
- :host {
16619
- min-width: 320px;
16620
- min-height: 150px;
16621
- }
16622
- .content {
17697
+ .${prefix} {
16623
17698
  width: 100%;
16624
17699
  height: 100%;
16625
17700
  display: flex;
@@ -16628,17 +17703,15 @@ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
16628
17703
  border-radius: 5px;
16629
17704
  overflow: hidden;
16630
17705
  cursor: pointer;
16631
- background-image: url("${mobilePrimaryImage}");
17706
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
16632
17707
  background-size: cover;
16633
17708
  background-position: center;
16634
- background-blend-mode: overlay;
16635
17709
  background-repeat: no-repeat;
17710
+ container-type: inline-size;
17711
+ position: relative;
16636
17712
  }
16637
- .text {
16638
- padding: 15px 10%;
16639
- width: fit-content;
16640
- }
16641
- .header {
17713
+
17714
+ .${prefix}__header {
16642
17715
  font-size: 16px;
16643
17716
  color: ${textColor};
16644
17717
  line-height: 20px;
@@ -16646,103 +17719,107 @@ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
16646
17719
  font-family: "Source Sans 3", system-ui;
16647
17720
  font-style: normal;
16648
17721
  margin: 0;
17722
+ padding: 15px 10%;
16649
17723
  }
16650
- @media (min-width: 640px) {
16651
- .content {
16652
- background-image: url("${primaryImage}");
17724
+
17725
+ @container (min-width: 640px) {
17726
+ .${prefix} {
17727
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
16653
17728
  }
16654
17729
  }
16655
- @media (min-width: 768px) {
16656
- .header {
17730
+
17731
+ @container (min-width: 768px) {
17732
+ .${prefix}__header {
16657
17733
  font-size: 22px;
16658
17734
  }
16659
17735
  }
16660
- @media (min-width: 1024px) {
16661
- .header {
17736
+
17737
+ @container (min-width: 1024px) {
17738
+ .${prefix}__header {
16662
17739
  font-size: 24px;
16663
17740
  }
16664
17741
  }
16665
- @media (min-width: 1280px) {
16666
- .header {
17742
+
17743
+ @container (min-width: 1280px) {
17744
+ .${prefix}__header {
16667
17745
  font-size: 28px;
16668
17746
  }
16669
17747
  }
16670
17748
  </style>
16671
17749
  `;
16672
- function rbNavigationBannerTemplate(spot) {
17750
+ };
17751
+ function rbNavigationBannerTemplate(spot, config) {
17752
+ const { prefix = '' } = config;
16673
17753
  return `
16674
17754
  ${GFONT_PRECONNECT}
16675
17755
  ${GFONT_SOURCE_SANS_3}
16676
- ${STYLES$2(spot)}
16677
- <div class="content">
16678
- <div class="text">
16679
- ${spot.header ? `<h2 class="header">${spot.header}</h2>` : ''}
16680
- </div>
17756
+ ${STYLES$2(spot, config)}
17757
+ <div class="${prefix}">
17758
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
16681
17759
  </div>
16682
17760
  `;
16683
17761
  }
16684
17762
 
16685
- const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage, }) => `
16686
- <style>
16687
- :host {
16688
- min-width: 165px;
16689
- min-height: 300px;
16690
- }
16691
- .content {
16692
- width: 100%;
16693
- height: 100%;
16694
- display: flex;
16695
- flex-direction: column;
16696
- justify-content: flex-end;
16697
- background-image: url("${mobilePrimaryImage}");
16698
- background-size: cover;
16699
- background-repeat: no-repeat;
16700
- background-position: center;
16701
- border-radius: 5px;
16702
- overflow: hidden;
16703
- cursor: pointer;
16704
- }
16705
- .text {
16706
- padding: 10px;
16707
- width: 70%;
16708
- }
16709
- .header {
16710
- font-size: 12px;
16711
- color: ${textColor};
16712
- line-height: 16px;
16713
- font-family: "Source Sans 3", system-ui;
16714
- font-style: normal;
16715
- font-weight: 400;
16716
- line-height: normal;
16717
- margin: 0;
16718
- }
16719
- @media (min-width: 640px) {
16720
- .content {
16721
- background-image: url("${primaryImage}");
17763
+ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
17764
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
17765
+ return `
17766
+ <style>
17767
+ .${prefix} {
17768
+ width: 100%;
17769
+ height: 100%;
17770
+ display: flex;
17771
+ flex-direction: column;
17772
+ justify-content: flex-end;
17773
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
17774
+ background-size: cover;
17775
+ background-repeat: no-repeat;
17776
+ background-position: center;
17777
+ border-radius: 5px;
17778
+ overflow: hidden;
17779
+ cursor: pointer;
17780
+ container-type: inline-size;
17781
+ }
17782
+
17783
+ .${prefix}__text {
17784
+ padding: 10px;
17785
+ width: 70%;
17786
+ }
17787
+
17788
+ .${prefix}__header {
17789
+ font-size: 12px;
17790
+ color: ${textColor};
17791
+ line-height: 16px;
17792
+ font-family: "Source Sans 3", system-ui;
17793
+ font-style: normal;
17794
+ font-weight: 400;
17795
+ margin: 0;
16722
17796
  }
16723
- }
16724
- </style>
16725
- `;
16726
- function rbSmallCategoryImageToutTemplate(spot) {
17797
+
17798
+ @container (min-width: 640px) {
17799
+ .${prefix} {
17800
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
17801
+ }
17802
+ }
17803
+ </style>
17804
+ `;
17805
+ };
17806
+ function rbSmallCategoryImageToutTemplate(spot, config) {
17807
+ const { prefix = '' } = config;
16727
17808
  return `
16728
17809
  ${GFONT_PRECONNECT}
16729
17810
  ${GFONT_CORMORANT}
16730
- ${STYLES$1(spot)}
16731
- <div class="content">
16732
- <div class="text">
16733
- ${spot.header ? `<h2 class="header">${spot.header}</h2>` : ''}
17811
+ ${STYLES$1(spot, config)}
17812
+ <div class="${prefix}">
17813
+ <div class="${prefix}__text">
17814
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
16734
17815
  </div>
16735
17816
  </div>
16736
17817
  `;
16737
17818
  }
16738
17819
 
16739
- const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primaryImage, mobilePrimaryImage = primaryImage, }) => `
17820
+ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
16740
17821
  <style>
16741
- :host {
16742
- min-width: 165px;
16743
- min-height: 250px;
16744
- }
16745
- .content {
17822
+ .${prefix} {
16746
17823
  width: 100%;
16747
17824
  height: 100%;
16748
17825
  background-color: ${backgroundColor};
@@ -16750,8 +17827,10 @@ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primar
16750
17827
  display: flex;
16751
17828
  flex-direction: column;
16752
17829
  border-radius: 5px;
17830
+ container-type: inline-size;
16753
17831
  }
16754
- .image {
17832
+
17833
+ .${prefix}__image {
16755
17834
  width: 100%;
16756
17835
  height: 100%;
16757
17836
  background-image: url("${mobilePrimaryImage}");
@@ -16760,7 +17839,8 @@ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primar
16760
17839
  background-position: center;
16761
17840
  border-radius: 5px;
16762
17841
  }
16763
- .text {
17842
+
17843
+ .${prefix}__text {
16764
17844
  text-align: left;
16765
17845
  display: flex;
16766
17846
  flex-direction: row;
@@ -16770,7 +17850,8 @@ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primar
16770
17850
  height: fit-content;
16771
17851
  position: relative;
16772
17852
  }
16773
- .header {
17853
+
17854
+ .${prefix}__header {
16774
17855
  font-size: 12px;
16775
17856
  color: ${textColor};
16776
17857
  padding-top: 5px;
@@ -16778,46 +17859,42 @@ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primar
16778
17859
  font-family: "Source Sans 3", system-ui;
16779
17860
  font-style: normal;
16780
17861
  font-weight: 400;
16781
- line-height: normal;
16782
17862
  margin: 0;
16783
17863
  }
16784
- @media (min-width: 640px) {
16785
- .image {
17864
+
17865
+ @container (min-width: 640px) {
17866
+ .${prefix}__image {
16786
17867
  background-image: url("${primaryImage}");
16787
17868
  }
16788
17869
  }
16789
17870
  </style>
16790
17871
  `;
16791
- function rbSmallDiscoverToutTemplate(spot) {
17872
+ function rbSmallDiscoverToutTemplate(spot, config) {
17873
+ const { prefix = '' } = config;
16792
17874
  return `
16793
17875
  ${GFONT_PRECONNECT}
16794
17876
  ${GFONT_CORMORANT}
16795
- ${STYLES(spot)}
16796
- <div class="content">
16797
- <div class="image"></div>
16798
- <div class="text">
16799
- ${spot.header ? `<h2 class="header">${spot.header}</h2>` : ''}
17877
+ ${STYLES(spot, config)}
17878
+ <div class="${prefix}">
17879
+ <div class="${prefix}__image"></div>
17880
+ <div class="${prefix}__text">
17881
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
16800
17882
  </div>
16801
17883
  </div>
16802
17884
  `;
16803
17885
  }
16804
17886
 
16805
- var ENUM_SPOT_ELEMENT_ATTRIBUTE;
16806
- (function (ENUM_SPOT_ELEMENT_ATTRIBUTE) {
16807
- ENUM_SPOT_ELEMENT_ATTRIBUTE["WIDTH"] = "width";
16808
- ENUM_SPOT_ELEMENT_ATTRIBUTE["HEIGHT"] = "height";
16809
- ENUM_SPOT_ELEMENT_ATTRIBUTE["FLUID"] = "fluid";
16810
- ENUM_SPOT_ELEMENT_ATTRIBUTE["REDIRECT_ON_CLICK"] = "redirect-on-click";
16811
- })(ENUM_SPOT_ELEMENT_ATTRIBUTE || (ENUM_SPOT_ELEMENT_ATTRIBUTE = {}));
16812
17887
  /**
16813
- * Creates the spot html string based on the provided spot data.
17888
+ * Returns the HTML element for the given spot.
16814
17889
  *
16815
- * @param {ISpot} data - The spot data.
17890
+ * @param {ISpot} spot - The spot object.
17891
+ * @param {ISpotTemplateConfig} config - The spot template configuration.
16816
17892
  *
16817
- * @return {string} - The spot html string.
17893
+ * @return {HTMLElement | null} - The HTML element for the given spot or null if the spot template is not found.
16818
17894
  */
16819
- const GET_SPOT_TEMPLATE_HTML_STRING = (data) => {
17895
+ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
16820
17896
  const templates = {
17897
+ // Reserve Bar Spot Templates
16821
17898
  [exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE]: {
16822
17899
  rbHomepageHeroThreeTile: rbHomepageHeroThreeTileTemplate,
16823
17900
  },
@@ -16845,6 +17922,7 @@ const GET_SPOT_TEMPLATE_HTML_STRING = (data) => {
16845
17922
  [exports.RMN_SPOT_TYPE.RB_PRODUCT_UPCS]: {
16846
17923
  rbProductUpcs: () => '', // No template for this spot type, it will be handled by ReserveBar App.
16847
17924
  },
17925
+ // IAB Standard Spot Templates
16848
17926
  [exports.RMN_SPOT_TYPE.BILLBOARD]: {
16849
17927
  billboardV1: billboardV1Template,
16850
17928
  billboardV2: billboardV2Template,
@@ -16871,232 +17949,814 @@ const GET_SPOT_TEMPLATE_HTML_STRING = (data) => {
16871
17949
  inTextV1: inTextV1Template,
16872
17950
  },
16873
17951
  };
16874
- const spotVariants = templates[data.spot];
17952
+ const spotVariants = templates[spot.spot];
16875
17953
  if (!spotVariants) {
16876
- return '';
17954
+ return null;
16877
17955
  }
16878
- const variantTemplate = spotVariants[data.variant];
17956
+ const variantTemplate = spotVariants[spot.variant];
16879
17957
  if (!variantTemplate) {
16880
- return '';
17958
+ return null;
16881
17959
  }
16882
- return variantTemplate(data);
17960
+ // Generate a highly unique prefix to avoid conflicts with other elements.
17961
+ const prefix = 's' + UniqueIdGenerator.generate().toLowerCase();
17962
+ const spotHtmlString = variantTemplate(spot, { ...config, prefix });
17963
+ return spotHtmlStringToElement(spotHtmlString);
16883
17964
  };
16884
17965
 
16885
- let SpotElement;
16886
- if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') {
16887
- class CustomSpotElement extends HTMLElement {
16888
- constructor() {
16889
- super();
16890
- this.hasCustomContent = false;
16891
- this.handleClick = this.handleClick.bind(this);
16892
- this.attachShadow({ mode: 'open' });
17966
+ /**
17967
+ * PubSub class
17968
+ * Manages event subscriptions and publications
17969
+ * @template IEventMap A record type defining the structure of events and their data
17970
+ */
17971
+ class PubSub {
17972
+ constructor() {
17973
+ /**
17974
+ * Object to store subscribers for each event type
17975
+ */
17976
+ this.subscribers = {};
17977
+ }
17978
+ static getInstance() {
17979
+ if (!PubSub.instance) {
17980
+ PubSub.instance = new PubSub();
16893
17981
  }
16894
- connectedCallback() {
16895
- this.hasCustomContent = Boolean(this.customContent);
16896
- this.addEventListener('click', this.handleClick);
16897
- this.setupIntersectionObserver();
16898
- this.render();
17982
+ return PubSub.instance;
17983
+ }
17984
+ /**
17985
+ * Subscribe to an event
17986
+ * @param eventType - The type of event to subscribe to
17987
+ * @param callback - The function to be called when the event is published
17988
+ * @returns A function to unsubscribe from the event
17989
+ *
17990
+ * @Example:
17991
+ * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
17992
+ * console.log(`User ${data.username} logged in`);
17993
+ * });
17994
+ */
17995
+ subscribe(eventType, callback) {
17996
+ if (!this.subscribers[eventType]) {
17997
+ this.subscribers[eventType] = [];
16899
17998
  }
16900
- disconnectedCallback() {
16901
- var _a;
16902
- (_a = this.observer) === null || _a === void 0 ? void 0 : _a.disconnect();
16903
- this.removeEventListener('click', this.handleClick);
17999
+ this.subscribers[eventType].push(callback);
18000
+ // Return an unsubscribe function
18001
+ return () => {
18002
+ this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
18003
+ };
18004
+ }
18005
+ /**
18006
+ * Publish an event
18007
+ * @param eventType - The type of event to publish
18008
+ * @param data - The data to be passed to the event subscribers
18009
+ *
18010
+ * @Example:
18011
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18012
+ */
18013
+ publish(eventType, data) {
18014
+ if (!this.subscribers[eventType]) {
18015
+ return;
16904
18016
  }
16905
- attributeChangedCallback(_name, oldValue, newValue) {
16906
- if (oldValue !== newValue) {
16907
- this.render();
16908
- }
18017
+ this.subscribers[eventType].forEach((callback) => callback(data));
18018
+ }
18019
+ }
18020
+ /**
18021
+ * Usage Example:
18022
+ *
18023
+ * interface IEventMap {
18024
+ * userLogin: { username: string; timestamp: number };
18025
+ * pageView: { url: string; timestamp: number };
18026
+ * }
18027
+ *
18028
+ * const pubSub = new PubSub<IEventMap>();
18029
+ *
18030
+ * // Subscribe to events
18031
+ * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
18032
+ * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
18033
+ * });
18034
+ *
18035
+ * pubSub.subscribe('pageView', (data) => {
18036
+ * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
18037
+ * });
18038
+ *
18039
+ * // Publish events
18040
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18041
+ * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
18042
+ *
18043
+ * // Unsubscribe from an event
18044
+ * unsubscribeLogin();
18045
+ */
18046
+
18047
+ class EventService {
18048
+ constructor() {
18049
+ this.pubSub = PubSub.getInstance();
18050
+ this.localStorage = LocalStorage.getInstance();
18051
+ this.activeSpots = new Map();
18052
+ this.spotStates = new Map();
18053
+ this.intersectionObserver = new IntersectionObserverService();
18054
+ }
18055
+ static getInstance() {
18056
+ if (!EventService.instance) {
18057
+ EventService.instance = new EventService();
16909
18058
  }
16910
- render() {
16911
- if (!this.shadowRoot || !this.data)
16912
- return;
16913
- const { style, wrapper, slot } = SPOT_ELEMENT_TEMPLATE(this.data.width, this.data.height, this.hasCustomContent);
16914
- this.shadowRoot.replaceChildren(style, slot);
16915
- if (this.hasCustomContent) {
16916
- this.setCustomContent();
16917
- }
16918
- if (!this.hasCustomContent) {
16919
- wrapper.innerHTML = GET_SPOT_TEMPLATE_HTML_STRING(this.data);
16920
- this.shadowRoot.appendChild(wrapper);
16921
- }
18059
+ return EventService.instance;
18060
+ }
18061
+ subscribe(eventType, callback) {
18062
+ return this.pubSub.subscribe(eventType, callback);
18063
+ }
18064
+ publish(eventType, data) {
18065
+ this.pubSub.publish(eventType, data);
18066
+ }
18067
+ registerSpot(params) {
18068
+ const { placementId, spot, spotElement } = params;
18069
+ this.activeSpots.set(placementId, { spotElement });
18070
+ // Fire impression event
18071
+ this.fireImpressionEvent(placementId, spot, spotElement);
18072
+ // Handle intersection observer
18073
+ this.handleIntersectionObserver(placementId, spot, spotElement);
18074
+ // Attach click event listener
18075
+ spotElement.addEventListener('click', async () => await this.handleClick(params));
18076
+ }
18077
+ unregisterSpot(placementId) {
18078
+ const placementIdClean = placementId.replace('#', '');
18079
+ const spotData = this.activeSpots.get(placementIdClean);
18080
+ if (!spotData) {
18081
+ this.handleSpotState(placementIdClean, {
18082
+ state: {
18083
+ error: `Active spot with placementId ${placementIdClean} not found.`,
18084
+ },
18085
+ });
18086
+ return;
16922
18087
  }
16923
- setCustomContent() {
16924
- const wrapper = document.createElement('div');
16925
- wrapper.setAttribute('slot', SPOT_ELEMENT_SLOT_NAME);
16926
- if (typeof this.customContent === 'string') {
16927
- wrapper.innerHTML = this.customContent;
16928
- }
16929
- if (this.customContent instanceof HTMLElement) {
16930
- wrapper.appendChild(this.customContent);
16931
- }
16932
- this.appendChild(wrapper);
18088
+ this.intersectionObserver.unobserve(spotData.spotElement);
18089
+ this.handleSpotState(placementIdClean, {
18090
+ dom: {
18091
+ spotElement: undefined,
18092
+ visibleOnViewport: false,
18093
+ },
18094
+ state: {
18095
+ unmounted: true,
18096
+ mounted: false,
18097
+ },
18098
+ });
18099
+ this.activeSpots.delete(placementIdClean);
18100
+ const placementElement = document.getElementById(placementIdClean);
18101
+ if (!placementElement) {
18102
+ this.handleSpotState(placementIdClean, {
18103
+ state: {
18104
+ error: `Placement element with id ${placementIdClean} not found.`,
18105
+ },
18106
+ });
18107
+ return;
16933
18108
  }
16934
- setupIntersectionObserver() {
16935
- const options = {
16936
- root: null,
16937
- rootMargin: '0px',
16938
- threshold: 0.5, // The element is considered visible when 50% of it is visible
18109
+ placementElement.innerHTML = '';
18110
+ }
18111
+ /**
18112
+ * Updates the state of a spot.
18113
+ *
18114
+ * @param {string} placementId - The placement ID of the spot.
18115
+ * @param {Partial<ILifecycleState>} updates - The updates to apply to the spot state.
18116
+ * @param {boolean} publish - Whether to publish the updated state.
18117
+ *
18118
+ * @returns {void}
18119
+ */
18120
+ handleSpotState(placementId, updates, publish = true) {
18121
+ let currentState = this.spotStates.get(placementId);
18122
+ if (!currentState) {
18123
+ currentState = {
18124
+ identifier: {
18125
+ placementId,
18126
+ spotId: '',
18127
+ spotType: '',
18128
+ },
18129
+ dom: {
18130
+ spotElement: undefined,
18131
+ visibleOnViewport: false,
18132
+ },
18133
+ state: {
18134
+ mounted: false,
18135
+ unmounted: false,
18136
+ loading: false,
18137
+ error: undefined,
18138
+ },
18139
+ displayConfig: {
18140
+ isCarousel: false,
18141
+ isCarouselItem: false,
18142
+ isSingleItem: false,
18143
+ },
16939
18144
  };
16940
- this.observer = new IntersectionObserver((entries) => {
16941
- var _a;
16942
- if (entries[0].isIntersecting) {
16943
- this.registerEvent(exports.RMN_SPOT_EVENT.IMPRESSION);
16944
- (_a = this.observer) === null || _a === void 0 ? void 0 : _a.disconnect();
16945
- }
16946
- }, options);
16947
- this.observer.observe(this);
16948
- }
16949
- handleClick() {
16950
- this.registerEvent(exports.RMN_SPOT_EVENT.CLICK);
16951
- }
16952
- async registerEvent(event) {
16953
- var _a, _b;
16954
- if (!this.data)
16955
- return;
16956
- const shouldRedirectOnClick = this.getAttribute(ENUM_SPOT_ELEMENT_ATTRIBUTE.REDIRECT_ON_CLICK) === 'true';
16957
- const eventUrl = (_b = (_a = this.data.events.find((e) => e.event === event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '';
16958
- try {
16959
- const options = {
16960
- method: 'POST',
16961
- redirect: event === exports.RMN_SPOT_EVENT.CLICK && shouldRedirectOnClick ? 'follow' : 'manual',
16962
- };
16963
- const response = await fetch(eventUrl, options);
16964
- if (response.ok && event === exports.RMN_SPOT_EVENT.CLICK && shouldRedirectOnClick) {
16965
- window.location.href = this.getRedirectUrlFromPayload(eventUrl);
16966
- }
16967
- }
16968
- catch (error) {
16969
- console.error(`Rmn error sending ${event} event:`, error);
16970
- }
16971
18145
  }
16972
- getRedirectUrlFromPayload(url) {
16973
- var _a, _b;
16974
- const base64String = (_a = new URL(url).searchParams.get('e')) !== null && _a !== void 0 ? _a : '';
16975
- try {
16976
- const data = JSON.parse(atob(base64String));
16977
- return (_b = data.ur) !== null && _b !== void 0 ? _b : '';
16978
- }
16979
- catch (_c) {
16980
- return '';
16981
- }
18146
+ this.spotStates.set(placementId, { ...currentState, ...updates });
18147
+ if (publish) {
18148
+ this.pubSub.publish(exports.RMN_SPOT_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
16982
18149
  }
16983
18150
  }
16984
- CustomSpotElement.observedAttributes = Object.values(ENUM_SPOT_ELEMENT_ATTRIBUTE);
16985
- SpotElement = CustomSpotElement;
16986
- }
16987
-
16988
- class SpotHtmlService {
16989
- static getInstance() {
16990
- return SingletonManager.getInstance('SpotHtmlService', () => new SpotHtmlService());
18151
+ async handleClick({ placementId, spot, spotElement, }) {
18152
+ var _a, _b, _c;
18153
+ // Publish click event
18154
+ this.pubSub.publish(exports.RMN_SPOT_EVENT.CLICK, {
18155
+ placementId,
18156
+ spotId: spot.id,
18157
+ spotElement,
18158
+ });
18159
+ // Fire click event
18160
+ await this.fireEvent({
18161
+ event: exports.RMN_SPOT_EVENT.CLICK,
18162
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.CLICK)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18163
+ });
18164
+ // Save spot to local storage for event tracking
18165
+ this.localStorage.setSpot(spot.id, {
18166
+ spotId: spot.id,
18167
+ spotType: spot.spot,
18168
+ events: spot.events,
18169
+ productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [1, 'GROUPING-12345', 'DAN-12345', 131398103],
18170
+ });
18171
+ }
18172
+ handleIntersectionObserver(placementId, _spot, spotElement) {
18173
+ const spotIsVisibleCallback = async () => {
18174
+ this.intersectionObserver.unobserve(spotElement);
18175
+ this.handleSpotState(placementId, {
18176
+ dom: {
18177
+ spotElement,
18178
+ visibleOnViewport: true,
18179
+ },
18180
+ });
18181
+ };
18182
+ this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
18183
+ }
18184
+ fireImpressionEvent(placementId, spot, spotElement) {
18185
+ this.pubSub.publish(exports.RMN_SPOT_EVENT.IMPRESSION, {
18186
+ placementId,
18187
+ spotId: spot.id,
18188
+ spotElement,
18189
+ });
18190
+ (async () => {
18191
+ var _a, _b;
18192
+ await this.fireEvent({
18193
+ event: exports.RMN_SPOT_EVENT.IMPRESSION,
18194
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.IMPRESSION)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18195
+ });
18196
+ })();
16991
18197
  }
16992
18198
  /**
16993
- * Creates the html element based on the provided spot data using shadow dom.
18199
+ * Fires an event using the navigator.sendBeacon method and redirects the user if the event is a click event.
16994
18200
  *
16995
- * This method is only available in browser environments.
16996
- *
16997
- * @param {ISpot} spot - The spot data.
16998
- * @param {ICreateSpotElementConfig} config - The configuration object.
16999
- * @param {ICreateSpotElementConfig.fluid} config.fluid - If the spot should be fluid or not.
17000
- * @param {ICreateSpotElementConfig.customContent} config.customContent - Use a custom html element/string.
17001
- * @param {ICreateSpotElementConfig.redirectOnClick} config.redirectOnClick - If the spot should redirect on click.
17002
- *
17003
- * @return {HTMLElement | null} - The spot html element or null if the browser environment is not available.
18201
+ * @param {IFireEventParams} params - The parameters for firing the event.
18202
+ * @param {RMN_SPOT_EVENT} params.event - The event type.
18203
+ * @param {string} params.eventUrl - The URL to which the event is sent.
18204
+ * @returns {Promise<void>} - A promise that resolves when the event is fired.
17004
18205
  */
17005
- createSpotHtmlElement(spot, config) {
17006
- var _a, _b;
17007
- if (!this.ensureBrowserEnvironmentAndDefineElement()) {
17008
- return null;
18206
+ async fireEvent({ event, eventUrl }) {
18207
+ const didFireEvent = navigator.sendBeacon(eventUrl);
18208
+ if (didFireEvent && event === exports.RMN_SPOT_EVENT.CLICK) {
18209
+ window.location.href = this.getRedirectUrlFromPayload(eventUrl);
17009
18210
  }
17010
- const isFluid = (_a = config === null || config === void 0 ? void 0 : config.fluid) !== null && _a !== void 0 ? _a : false;
17011
- const shouldRedirectOnClick = (_b = config === null || config === void 0 ? void 0 : config.redirectOnClick) !== null && _b !== void 0 ? _b : true;
17012
- const element = document.createElement(SPOT_ELEMENT_TAG);
17013
- element.setAttribute(ENUM_SPOT_ELEMENT_ATTRIBUTE.WIDTH, spot.width.toString());
17014
- element.setAttribute(ENUM_SPOT_ELEMENT_ATTRIBUTE.HEIGHT, spot.height.toString());
17015
- element.setAttribute(ENUM_SPOT_ELEMENT_ATTRIBUTE.FLUID, isFluid.toString());
17016
- element.setAttribute(ENUM_SPOT_ELEMENT_ATTRIBUTE.REDIRECT_ON_CLICK, shouldRedirectOnClick.toString());
17017
- // Share the spot data with the element
17018
- element.data = spot;
17019
- // Set custom content
17020
- if (config === null || config === void 0 ? void 0 : config.customContent) {
17021
- element.customContent = config.customContent;
17022
- }
17023
- return element;
17024
18211
  }
17025
18212
  /**
17026
- * @returns {boolean} - True if the browser environment is available and the element is defined.
18213
+ * Extracts and decodes a URL from a base64-encoded query parameter.
18214
+ *
18215
+ * @param {string} url - The URL containing the base64-encoded query parameter.
18216
+ * @returns {string} - The decoded URL or an empty string if decoding fails.
17027
18217
  */
17028
- ensureBrowserEnvironmentAndDefineElement() {
17029
- if (typeof window === 'undefined' || typeof document === 'undefined') {
17030
- console.warn('LiquidCommerce Rmn Sdk: createSpotElement is only available in browser environments!!!');
17031
- return false;
18218
+ getRedirectUrlFromPayload(url) {
18219
+ var _a, _b;
18220
+ const base64String = (_a = new URL(url).searchParams.get('e')) !== null && _a !== void 0 ? _a : '';
18221
+ try {
18222
+ const data = JSON.parse(atob(base64String));
18223
+ return (_b = data.ur) !== null && _b !== void 0 ? _b : '';
17032
18224
  }
17033
- if (!customElements.get(SPOT_ELEMENT_TAG)) {
17034
- customElements.define(SPOT_ELEMENT_TAG, SpotElement);
18225
+ catch (_c) {
18226
+ return '';
17035
18227
  }
17036
- return true;
17037
18228
  }
17038
18229
  }
17039
18230
 
17040
- class SpotSelectionService extends BaseApi {
18231
+ const SELECTION_API_PATH = '/spots/selection';
18232
+
18233
+ class SelectionService extends BaseApi {
17041
18234
  constructor(auth) {
17042
18235
  super(auth);
17043
18236
  }
17044
18237
  static getInstance(auth) {
17045
- return SingletonManager.getInstance('SpotSelectionService', () => new SpotSelectionService(auth));
18238
+ return SingletonManager.getInstance('SelectionService', () => new SelectionService(auth));
17046
18239
  }
17047
18240
  /**
17048
18241
  * Makes a selection request on our server based on the provided data.
17049
18242
  *
17050
18243
  * @param {ISpotSelectionParams} data - Spots selection parameters.
17051
18244
  *
17052
- * @return {Promise<ISpots>} - The spots response object.
18245
+ * @return {Promise<ISpots | { error: string }>} - The spots response object.
17053
18246
  */
17054
18247
  async spotSelection(data) {
17055
- const { isOk, val, isErr } = await this.post(SPOTS_SELECTION_API_PATH, data, {});
18248
+ const { isOk, val, isErr } = await this.post(SELECTION_API_PATH, data, {});
17056
18249
  if (isErr) {
17057
- throw new Error(`There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})`);
18250
+ return { error: `There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})` };
17058
18251
  }
17059
18252
  if (isOk && val && val.data && (val === null || val === void 0 ? void 0 : val.refresh.token)) {
17060
18253
  this.authInfo.authenticated = true;
17061
18254
  this.authInfo.token = val.refresh.token;
17062
18255
  return val.data.spots;
17063
18256
  }
17064
- throw new Error('Spot selection response was not successful');
18257
+ return { error: 'Spot selection response was not successful' };
17065
18258
  }
17066
18259
  }
17067
18260
 
18261
+ const SPOT_EVENTS_EXAMPLE = [
18262
+ {
18263
+ event: exports.RMN_SPOT_EVENT.CLICK,
18264
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwidXIiOm51bGx9&s=hWz37kbxi_u95EVNn2aoQhc5Aas',
18265
+ },
18266
+ {
18267
+ event: exports.RMN_SPOT_EVENT.IMPRESSION,
18268
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiYmEiOjEsImZxIjowfQ&s=djoysjCimurf-5T11AlNAwwLSS8',
18269
+ },
18270
+ {
18271
+ event: exports.RMN_SPOT_EVENT.PURCHASE,
18272
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjU5fQ&s=AAPAw-3SfZ0JMzjEGFSwt9L-2S4',
18273
+ },
18274
+ {
18275
+ event: exports.RMN_SPOT_EVENT.ADD_TO_CART,
18276
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYwfQ&s=uzQFcjgL7m9XqUG8FvTPVN5YkZY',
18277
+ },
18278
+ {
18279
+ event: exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST,
18280
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYzfQ&s=m3ISU_iIy-OFtXrTKpI6cJAEC0k',
18281
+ },
18282
+ {
18283
+ event: exports.RMN_SPOT_EVENT.BUY_NOW,
18284
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjY5fQ&s=l6MOscQC-q-FkC2Ksd7w6jjySCQ',
18285
+ },
18286
+ ];
18287
+ const RB_SPOTS_SELECTION_EXAMPLE = {
18288
+ rbHomepageHeroFullImage: [
18289
+ {
18290
+ id: '111111_111111',
18291
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18292
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18293
+ width: 1140,
18294
+ height: 640,
18295
+ header: 'Artisanal Craft Beer Collection',
18296
+ description: 'Discover our curated selection of small-batch, flavor-packed craft beers.',
18297
+ ctaText: 'Explore the Collection',
18298
+ textColor: '#ffffff',
18299
+ ctaTextColor: '#ffffff',
18300
+ primaryImage: 'https://placehold.co/1140x640/png?text=Craft+Beer+Collection',
18301
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Craft+Beer',
18302
+ events: SPOT_EVENTS_EXAMPLE,
18303
+ },
18304
+ {
18305
+ id: '222222_222222',
18306
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18307
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18308
+ width: 1140,
18309
+ height: 640,
18310
+ header: 'Summer Wine Spectacular',
18311
+ description: 'Refresh your palate with our handpicked selection of crisp, summer wines.',
18312
+ ctaText: 'Shop Summer Wines',
18313
+ textColor: '#000000',
18314
+ ctaTextColor: '#ffffff',
18315
+ primaryImage: 'https://placehold.co/1140x640/png?text=Summer+Wines',
18316
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Summer+Wines',
18317
+ events: SPOT_EVENTS_EXAMPLE,
18318
+ },
18319
+ ],
18320
+ rbHomepageHeroTwoTile: [
18321
+ {
18322
+ id: '333333_333333',
18323
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
18324
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
18325
+ width: 1140,
18326
+ height: 640,
18327
+ header: 'Whiskey Wonderland',
18328
+ description: 'Embark on a journey through our premium whiskey selection.',
18329
+ ctaText: 'Discover Whiskeys',
18330
+ textColor: '#ffffff',
18331
+ backgroundColor: '#2c1a05',
18332
+ ctaTextColor: '#2c1a05',
18333
+ primaryImage: 'https://placehold.co/1140x640/png?text=Whiskey+Collection',
18334
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Whiskey',
18335
+ events: SPOT_EVENTS_EXAMPLE,
18336
+ },
18337
+ ],
18338
+ rbHomepageHeroThreeTile: [
18339
+ {
18340
+ id: '444444_444444',
18341
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
18342
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
18343
+ width: 1140,
18344
+ height: 640,
18345
+ header: 'Cocktail Essentials',
18346
+ description: 'Stock your bar with premium spirits and mixers for the perfect cocktail.',
18347
+ ctaText: 'Build Your Bar',
18348
+ textColor: '#ffffff',
18349
+ backgroundColor: '#1a3c4d',
18350
+ ctaTextColor: '#1a3c4d',
18351
+ primaryImage: 'https://placehold.co/1140x640/png?text=Cocktail+Spirits',
18352
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Cocktail+Mixers',
18353
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Cocktail+Kit',
18354
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Cocktail+Mixers',
18355
+ events: SPOT_EVENTS_EXAMPLE,
18356
+ },
18357
+ ],
18358
+ rbLargeCategoryImageTout: [
18359
+ {
18360
+ id: '555555_555555',
18361
+ spot: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
18362
+ variant: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
18363
+ width: 468,
18364
+ height: 410,
18365
+ header: 'Rare & Limited Edition',
18366
+ description: 'Discover our collection of hard-to-find and limited release spirits.',
18367
+ textColor: '#ffffff',
18368
+ ctaTextColor: '#ffffff',
18369
+ primaryImage: 'https://placehold.co/468x410/png?text=Rare+Spirits',
18370
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Rare+Spirits',
18371
+ ctaText: 'Shop Rare Spirits',
18372
+ events: SPOT_EVENTS_EXAMPLE,
18373
+ },
18374
+ ],
18375
+ rbSmallDiscoverTout: [
18376
+ {
18377
+ id: '666666_666666',
18378
+ spot: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
18379
+ variant: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
18380
+ width: 224,
18381
+ height: 378,
18382
+ header: 'Château Margaux 2015 Bordeaux',
18383
+ textColor: '#ffffff',
18384
+ primaryImage: 'https://placehold.co/224x378/png?text=Château+Margaux',
18385
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Château+Margaux',
18386
+ events: SPOT_EVENTS_EXAMPLE,
18387
+ },
18388
+ ],
18389
+ rbSmallCategoryImageTout: [
18390
+ {
18391
+ id: '777777_777777',
18392
+ spot: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
18393
+ variant: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
18394
+ width: 224,
18395
+ height: 410,
18396
+ header: 'Japanese Sake',
18397
+ textColor: '#ffffff',
18398
+ primaryImage: 'https://placehold.co/224x410/png?text=Japanese+Sake',
18399
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Japanese+Sake',
18400
+ events: SPOT_EVENTS_EXAMPLE,
18401
+ },
18402
+ ],
18403
+ rbCollectionBannerWithoutTextBlock: [
18404
+ {
18405
+ id: '888888_888888',
18406
+ spot: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
18407
+ variant: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
18408
+ width: 887,
18409
+ height: 344,
18410
+ primaryImage: 'https://placehold.co/887x344/png?text=Summer+Cocktails',
18411
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Summer+Cocktails',
18412
+ events: SPOT_EVENTS_EXAMPLE,
18413
+ },
18414
+ ],
18415
+ rbNavigationBanner: [
18416
+ {
18417
+ id: '999999_999999',
18418
+ spot: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
18419
+ variant: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
18420
+ width: 440,
18421
+ height: 220,
18422
+ header: 'Explore Tequilas',
18423
+ textColor: '#ffffff',
18424
+ primaryImage: 'https://placehold.co/440x220/png?text=Tequila+Collection',
18425
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Tequila+Collection',
18426
+ events: SPOT_EVENTS_EXAMPLE,
18427
+ },
18428
+ ],
18429
+ };
18430
+
17068
18431
  class LiquidCommerceRmnClient {
17069
18432
  constructor(auth) {
17070
- this.spotSelectionService = SpotSelectionService.getInstance(auth);
17071
- this.spotHtmlService = SpotHtmlService.getInstance();
18433
+ this.selectionService = SelectionService.getInstance(auth);
18434
+ this.elementService = ElementService.getInstance();
18435
+ this.eventService = EventService.getInstance();
17072
18436
  }
17073
18437
  /**
17074
18438
  * Makes a selection request on our server based on the provided data.
17075
18439
  *
17076
18440
  * To create a spot html element, use the RmnCreateSpotElement function.
17077
18441
  *
17078
- * @param {ISpotSelectionParams} data - Spots selection parameters.
18442
+ * @param {ISpotSelectionParams} params - Spots selection parameters.
17079
18443
  *
17080
- * @return {Promise<ISpots>} - The spots response object.
18444
+ * @return {Promise<ISpots | {error : string}>} - The spots response object.
17081
18445
  */
17082
- async spotSelection(data) {
17083
- return this.spotSelectionService.spotSelection(data);
18446
+ async spotSelection(params) {
18447
+ return this.selectionService.spotSelection(params);
18448
+ }
18449
+ /**
18450
+ * Injects the spot elements into their provided placement.
18451
+ *
18452
+ * @param {IInjectSpotElementParams} params - Parameters for injecting spot elements.
18453
+ *
18454
+ * @return {Promise<void>} - A promise that resolves when the spot elements are injected.
18455
+ */
18456
+ async injectSpotElement(params) {
18457
+ var _a;
18458
+ const config = params.config;
18459
+ let inject = params.inject;
18460
+ if (!inject.length) {
18461
+ this.eventService.handleSpotState('all', {
18462
+ state: {
18463
+ error: 'No spot elements provided for injection.',
18464
+ loading: false,
18465
+ },
18466
+ });
18467
+ return;
18468
+ }
18469
+ // Update the state of the spots to loading
18470
+ this.updateSpotsState(inject);
18471
+ // Prevent duplicate placement ids
18472
+ const hasDuplicatePlacementIds = this.preventDuplicateSpotPlacementIds(inject);
18473
+ if (!hasDuplicatePlacementIds) {
18474
+ return;
18475
+ }
18476
+ // Prevent non-existent spot types
18477
+ inject = this.preventNonExistentSpotTypes(inject);
18478
+ // Make the spot selection request
18479
+ const response = await this.spotSelectionRequest({ ...params, inject });
18480
+ // const response = await this.useSpotSelectionExample(inject);
18481
+ // Handle the response
18482
+ if (typeof response === 'object' && 'error' in response) {
18483
+ this.eventService.handleSpotState('all', {
18484
+ state: {
18485
+ error: response.error,
18486
+ },
18487
+ });
18488
+ return;
18489
+ }
18490
+ for (const item of inject) {
18491
+ const itemConfig = (_a = item.config) !== null && _a !== void 0 ? _a : config;
18492
+ const spots = response[item.placementId];
18493
+ if (!(spots === null || spots === void 0 ? void 0 : spots.length)) {
18494
+ this.eventService.handleSpotState(item.placementId, {
18495
+ state: {
18496
+ error: `No spots found for type "${item.spotType}".`,
18497
+ loading: false,
18498
+ },
18499
+ });
18500
+ continue;
18501
+ }
18502
+ const placementId = item.placementId.replace('#', '');
18503
+ const placement = document.getElementById(placementId);
18504
+ if (!placement) {
18505
+ this.eventService.handleSpotState(item.placementId, {
18506
+ state: {
18507
+ error: `Placement not found for id "${placementId}".`,
18508
+ loading: false,
18509
+ },
18510
+ });
18511
+ continue;
18512
+ }
18513
+ // Take over placement styles
18514
+ placement.removeAttribute('style');
18515
+ placement.removeAttribute('class');
18516
+ Object.assign(placement.style, {
18517
+ width: '100%',
18518
+ height: 'auto',
18519
+ display: 'flex',
18520
+ justifyContent: 'center',
18521
+ });
18522
+ if (spots.length === 1) {
18523
+ const isInjected = this.injectOneSpotElement(item, placement, spots[0], itemConfig);
18524
+ if (!isInjected) {
18525
+ continue;
18526
+ }
18527
+ }
18528
+ if (spots.length > 1) {
18529
+ const isInjected = this.injectCarouselSpotElement(placement, spots, itemConfig);
18530
+ if (!isInjected) {
18531
+ continue;
18532
+ }
18533
+ }
18534
+ }
18535
+ }
18536
+ /**
18537
+ * Makes a selection request on our server based on the provided data.
18538
+ *
18539
+ * @param {IInjectSpotElementParams} params - Parameters for injecting spot elements.
18540
+ *
18541
+ * @return {Promise<ISpots | {error: string}>} - The spots response object.
18542
+ */
18543
+ async spotSelectionRequest(params) {
18544
+ const { inject, filter, config } = params;
18545
+ const request = {
18546
+ url: config === null || config === void 0 ? void 0 : config.url,
18547
+ filter,
18548
+ spots: inject.map((item) => ({
18549
+ placementId: item.placementId,
18550
+ spot: item.spotType,
18551
+ count: item === null || item === void 0 ? void 0 : item.count,
18552
+ ...item === null || item === void 0 ? void 0 : item.filter,
18553
+ })),
18554
+ };
18555
+ return this.spotSelection(request);
17084
18556
  }
17085
18557
  /**
17086
- * Creates the spot html element based on the provided data using shadow dom.
18558
+ * Injects a carousel element with the provided spots into the placement.
18559
+ *
18560
+ * @param {HTMLElement} placement - The placement element.
18561
+ * @param {ISpot[]} spots - The spot data.
18562
+ * @param {IInjectSpotElementConfig} config - The configuration object.
17087
18563
  *
17088
- * This method is useful when you are initializing the client in a browser environment, so you can create the spot html element directly from the RmnClient instance.
18564
+ * @return {void}
18565
+ */
18566
+ injectCarouselSpotElement(placement, spots, config) {
18567
+ var _a;
18568
+ const carouselSlides = [];
18569
+ for (const spotItem of spots) {
18570
+ this.eventService.handleSpotState(placement.id, {
18571
+ displayConfig: {
18572
+ isCarousel: true,
18573
+ isCarouselItem: true,
18574
+ isSingleItem: false,
18575
+ },
18576
+ }, false);
18577
+ const spot = this.elementService.overrideSpotColors(spotItem, config === null || config === void 0 ? void 0 : config.colors);
18578
+ const content = SPOT_TEMPLATE_HTML_ELEMENT(spot, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
18579
+ if (!content) {
18580
+ this.eventService.handleSpotState(placement.id, {
18581
+ state: {
18582
+ error: `Failed to inject carousel spot item element. Could not create element for type "${spot.spot}".`,
18583
+ loading: false,
18584
+ },
18585
+ });
18586
+ continue;
18587
+ }
18588
+ this.eventService.registerSpot({
18589
+ spot,
18590
+ placementId: placement.id,
18591
+ spotElement: content,
18592
+ });
18593
+ carouselSlides.push(content);
18594
+ }
18595
+ // Get the max width and height of the spots
18596
+ const { maxWidth, maxHeight } = spots.reduce((max, spot) => ({
18597
+ maxWidth: Math.max(max.maxWidth, spot.width),
18598
+ maxHeight: Math.max(max.maxHeight, spot.height),
18599
+ }), { maxWidth: 0, maxHeight: 0 });
18600
+ // Create the carousel element
18601
+ const carouselElement = this.elementService.createCarouselElement({
18602
+ slides: carouselSlides,
18603
+ config: {
18604
+ fluid: config === null || config === void 0 ? void 0 : config.fluid,
18605
+ width: maxWidth,
18606
+ height: maxHeight,
18607
+ minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
18608
+ ...config === null || config === void 0 ? void 0 : config.carousel,
18609
+ },
18610
+ });
18611
+ if (!carouselElement) {
18612
+ this.eventService.handleSpotState(placement.id, {
18613
+ state: {
18614
+ error: `Failed to inject spot carousel element. Could not create spot carousel element.`,
18615
+ loading: false,
18616
+ },
18617
+ });
18618
+ return false;
18619
+ }
18620
+ placement.replaceChildren(carouselElement);
18621
+ this.eventService.handleSpotState(placement.id, {
18622
+ dom: {
18623
+ spotElement: carouselElement,
18624
+ },
18625
+ state: {
18626
+ mounted: true,
18627
+ loading: false,
18628
+ },
18629
+ });
18630
+ return true;
18631
+ }
18632
+ /**
18633
+ * Injects a single spot element into the provided placement.
17089
18634
  *
18635
+ * @param {IInjectSpotElement} injectItem - The inject item data.
18636
+ * @param {HTMLElement} placement - The placement element.
17090
18637
  * @param {ISpot} spot - The spot data.
17091
- * @param {ICreateSpotElementConfig} config - The configuration object.
17092
- * @param {ICreateSpotElementConfig.fluid} config.fluid - If the spot should be fluid or not.
17093
- * @param {ICreateSpotElementConfig.customContent} config.customContent - Use a custom html element/string.
17094
- * @param {ICreateSpotElementConfig.redirectOnClick} config.redirectOnClick - If the spot should redirect on click.
18638
+ * @param {IInjectSpotElementConfig} config - The configuration object.
18639
+ *
18640
+ * @return {void}
18641
+ */
18642
+ injectOneSpotElement(injectItem, placement, spot, config) {
18643
+ var _a;
18644
+ const spotData = this.elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
18645
+ this.eventService.handleSpotState(injectItem.placementId, {
18646
+ displayConfig: {
18647
+ isSingleItem: true,
18648
+ },
18649
+ }, false);
18650
+ // Create the spot template element
18651
+ const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
18652
+ if (!content) {
18653
+ this.eventService.handleSpotState(injectItem.placementId, {
18654
+ state: {
18655
+ error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
18656
+ loading: false,
18657
+ },
18658
+ });
18659
+ return false;
18660
+ }
18661
+ // Create the spot element
18662
+ const spotElement = this.elementService.createSpotElement({
18663
+ content,
18664
+ config: {
18665
+ fluid: config === null || config === void 0 ? void 0 : config.fluid,
18666
+ spot: spot.spot,
18667
+ width: spot.width,
18668
+ height: spot.height,
18669
+ minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
18670
+ },
18671
+ });
18672
+ if (!spotElement) {
18673
+ this.eventService.handleSpotState(injectItem.placementId, {
18674
+ state: {
18675
+ error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
18676
+ loading: false,
18677
+ },
18678
+ });
18679
+ return false;
18680
+ }
18681
+ this.eventService.registerSpot({
18682
+ spot: spotData,
18683
+ placementId: injectItem.placementId,
18684
+ spotElement,
18685
+ });
18686
+ placement.replaceChildren(spotElement);
18687
+ this.eventService.handleSpotState(injectItem.placementId, {
18688
+ dom: {
18689
+ spotElement,
18690
+ },
18691
+ state: {
18692
+ mounted: true,
18693
+ loading: false,
18694
+ },
18695
+ });
18696
+ return true;
18697
+ }
18698
+ /**
18699
+ * Prevents duplicate placement ids in the inject data.
18700
+ *
18701
+ * @param {IInjectSpotElement[]} inject - The inject data.
17095
18702
  *
17096
- * @return {HTMLElement | null} - The spot html element or null if the browser environment is not available.
18703
+ * @throws {Error} - If a duplicate placement id is found.
18704
+ *
18705
+ * @return {void}
17097
18706
  */
17098
- createSpotElement(spot, config) {
17099
- return this.spotHtmlService.createSpotHtmlElement(spot, config);
18707
+ preventDuplicateSpotPlacementIds(inject) {
18708
+ const placementIds = new Set();
18709
+ for (const item of inject) {
18710
+ if (placementIds.has(item.placementId)) {
18711
+ this.eventService.handleSpotState(item.placementId, {
18712
+ state: {
18713
+ error: `Duplicate placement id (${item.placementId}) found. Please provide a unique placement id for each spot element.`,
18714
+ },
18715
+ });
18716
+ return false;
18717
+ }
18718
+ placementIds.add(item.placementId);
18719
+ }
18720
+ return true;
18721
+ }
18722
+ preventNonExistentSpotTypes(inject) {
18723
+ const newInject = [];
18724
+ for (const item of inject) {
18725
+ if (!Object.values(exports.RMN_SPOT_TYPE).includes(item.spotType)) {
18726
+ this.eventService.handleSpotState(item.placementId, {
18727
+ state: {
18728
+ error: `Invalid spot type (${item.spotType}) found. Please provide a valid spot type for each spot element.`,
18729
+ },
18730
+ });
18731
+ continue;
18732
+ }
18733
+ newInject.push(item);
18734
+ }
18735
+ return newInject;
18736
+ }
18737
+ updateSpotsState(inject) {
18738
+ for (const item of inject) {
18739
+ this.eventService.handleSpotState(item.placementId, {
18740
+ identifier: {
18741
+ placementId: item.placementId,
18742
+ spotType: item.spotType,
18743
+ },
18744
+ state: {
18745
+ loading: true,
18746
+ },
18747
+ });
18748
+ }
18749
+ }
18750
+ useSpotSelectionExample(inject) {
18751
+ const examples = RB_SPOTS_SELECTION_EXAMPLE;
18752
+ const data = {};
18753
+ inject.map((item) => {
18754
+ var _a, _b, _c;
18755
+ data[item.placementId] = (_c = (_a = examples[item.spotType]) === null || _a === void 0 ? void 0 : _a.slice(0, (_b = item.count) !== null && _b !== void 0 ? _b : 1)) !== null && _c !== void 0 ? _c : [];
18756
+ });
18757
+ return new Promise((resolve) => {
18758
+ resolve(data);
18759
+ });
17100
18760
  }
17101
18761
  }
17102
18762
  /**
@@ -17112,26 +18772,69 @@ async function RmnClient(apiKey, config) {
17112
18772
  const credentials = await authService.initialize();
17113
18773
  return new LiquidCommerceRmnClient(credentials);
17114
18774
  }
18775
+ /**
18776
+ * Creates a new instance of the RmnEventManager.
18777
+ *
18778
+ * @return {IRmnEventManager} - The RmnEventManager instance.
18779
+ */
18780
+ function RmnEventManager() {
18781
+ const eventService = EventService.getInstance();
18782
+ return {
18783
+ /**
18784
+ * Subscribes to an event type.
18785
+ */
18786
+ subscribe: (eventType, callback
18787
+ /* eslint-disable arrow-body-style */
18788
+ ) => {
18789
+ return eventService.subscribe(eventType, callback);
18790
+ },
18791
+ /**
18792
+ * Publishes an event type.
18793
+ */
18794
+ publish: (eventType, data) => {
18795
+ eventService.publish(eventType, data);
18796
+ },
18797
+ /**
18798
+ * Destroys a spot element
18799
+ */
18800
+ destroySpot: (placementId) => {
18801
+ eventService.unregisterSpot(placementId);
18802
+ },
18803
+ };
18804
+ }
17115
18805
  /**
17116
18806
  * Creates the spot html element based on the provided data using shadow dom.
17117
18807
  *
17118
18808
  * This method is useful when you are initializing the client in a non-browser environment.
17119
- * When you request a spot selection, you will receive the spot data in server-side and return them back to the client.
18809
+ * When you request a spot selection, you will receive the spot data in server-side and return them to the client.
17120
18810
  * Then you can use this function to create the spot html element based on the provided data without the need of the RmnClient instance.
17121
18811
  *
17122
18812
  * @param {ISpot} spot - The spot data.
17123
- * @param {ICreateSpotElementConfig} config - The configuration object.
17124
- * @param {ICreateSpotElementConfig.fluid} config.fluid - If the spot should be fluid or not.
17125
- * @param {ICreateSpotElementConfig.customContent} config.customContent - Use a custom html element/string.
17126
- * @param {ICreateSpotElementConfig.redirectOnClick} config.redirectOnClick - If the spot should redirect on click.
18813
+ * @param {IRmnCreateSpotElementConfig} config - The configuration object.
17127
18814
  *
17128
18815
  * @return {HTMLElement | null} - The spot html element or null if the browser environment is not available.
17129
18816
  */
17130
18817
  function RmnCreateSpotElement(spot, config) {
17131
- const spotHtmlService = SpotHtmlService.getInstance();
17132
- return spotHtmlService.createSpotHtmlElement(spot, config);
18818
+ var _a;
18819
+ const elementService = ElementService.getInstance();
18820
+ const spotData = elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
18821
+ const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
18822
+ if (!content) {
18823
+ console.warn(`RmnSdk: Failed to create spot element for type "${spotData.spot}".`);
18824
+ return null;
18825
+ }
18826
+ return elementService.createSpotElement({
18827
+ content,
18828
+ config: {
18829
+ fluid: true,
18830
+ width: spot.width,
18831
+ height: spot.height,
18832
+ minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
18833
+ },
18834
+ });
17133
18835
  }
17134
18836
 
17135
18837
  exports.LiquidCommerceRmnClient = LiquidCommerceRmnClient;
17136
18838
  exports.RmnClient = RmnClient;
17137
18839
  exports.RmnCreateSpotElement = RmnCreateSpotElement;
18840
+ exports.RmnEventManager = RmnEventManager;