@betarena/ad-engine 0.3.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,6 @@
22
22
  -->
23
23
 
24
24
  <script lang="ts">
25
-
26
25
  // #region ➤ 📦 Package Imports
27
26
 
28
27
  // ╭────────────────────────────────────────────────────────────────────────╮
@@ -38,27 +37,27 @@
38
37
  // │ 5. type(s) imports(s) │
39
38
  // ╰────────────────────────────────────────────────────────────────────────╯
40
39
 
41
- import { onDestroy, onMount, SvelteComponent } from 'svelte';
40
+ import { onDestroy, onMount, SvelteComponent } from "svelte";
42
41
  // import '@fontsource/roboto';
43
42
 
44
- import { ServiceAdEngine } from '@betarena/scores-lib/dist/classes/_service.adengine.js';
45
- import { removeNull } from '@betarena/scores-lib/dist/util/common.js';
46
- import { betarenaAdEngineStore } from './_store.js';
47
- import { betarenaEndpoint } from './constants/instance.js';
48
- import { storeSession } from './store/session.js';
49
- import { logger } from './utils/debug.js';
50
- import { detectDeviceWithUA } from './utils/device.js';
51
- import { getUserLocation } from './utils/geo.js';
52
-
53
- import WidgetAdGeneral from './Advert-General-Child.svelte';
54
- import AdvertInterScrollerChild from './Advert-InterScroller-Child.svelte';
55
- import AdvertLeftSide from './Advert-LeftSide-Child.svelte';
56
- import WidgetAdvertSlide from './Advert-Slide-Child.svelte';
57
- import DevInfoBox from './misc/admin/Dev-Info-Box.svelte';
58
-
59
- import type { IAdsServiceData } from '@betarena/scores-lib/types/ad-engine/index.js';
60
- import type { AdsCreativeMain } from '@betarena/scores-lib/types/v8/_HASURA-0.js';
61
- import type { GeoJsResponse } from './types/geojs.js';
43
+ import { ServiceAdEngine } from "@betarena/scores-lib/dist/classes/_service.adengine.js";
44
+ import { removeNull } from "@betarena/scores-lib/dist/util/common.js";
45
+ import { betarenaAdEngineStore } from "./_store.js";
46
+ import { betarenaEndpoint } from "./constants/instance.js";
47
+ import { storeSession } from "./store/session.js";
48
+ import { logger } from "./utils/debug.js";
49
+ import { detectDeviceWithUA } from "./utils/device.js";
50
+ import { getUserLocation } from "./utils/geo.js";
51
+
52
+ import WidgetAdGeneral from "./Advert-General-Child.svelte";
53
+ import AdvertInterScrollerChild from "./Advert-InterScroller-Child.svelte";
54
+ import AdvertLeftSide from "./Advert-LeftSide-Child.svelte";
55
+ import WidgetAdvertSlide from "./Advert-Slide-Child.svelte";
56
+ import DevInfoBox from "./misc/admin/Dev-Info-Box.svelte";
57
+
58
+ import type { IAdsServiceData } from "@betarena/scores-lib/types/ad-engine/index.js";
59
+ import type { AdsCreativeMain } from "@betarena/scores-lib/types/v8/_HASURA-0.js";
60
+ import type { GeoJsResponse } from "./types/geojs.js";
62
61
 
63
62
  // #endregion ➤ 📦 Package Imports
64
63
 
@@ -76,8 +75,7 @@
76
75
  // │ 4. $: [..] │
77
76
  // ╰────────────────────────────────────────────────────────────────────────╯
78
77
 
79
- export let
80
- /**
78
+ export let /**
81
79
  * @description
82
80
  * 📝 Width device change onset number
83
81
  */
@@ -94,27 +92,21 @@
94
92
  authorArticleTagIds: number[] = [],
95
93
  /**
96
94
  * @description
97
- * 📝 Dark theme value
95
+ * 📝 Logged-in users should bypass the initial global slider delay.
98
96
  */
99
- isDarkTheme: boolean = false,
97
+ isLoggedIn = false,
100
98
  /**
101
99
  * @description
102
100
  * 📝 Translation target
103
101
  */
104
- strTranslationTarget = 'en',
105
- /**
106
- * @description
107
- * 📝 Condition for testing
108
- */
109
- isStandalone = true,
102
+ strTranslationTarget = "en",
110
103
  /**
111
104
  * @description
112
105
  * 📝 Signal from the host that the current page is an article page.
113
106
  * Zone 1 body-mounted ads (slider/popup) must only inject when this is `true`.
114
107
  * Defaults to `false` so the widget fails closed on non-article pages.
115
108
  */
116
- isArticlePage: boolean = false
117
- ;
109
+ isArticlePage: boolean = false;
118
110
 
119
111
  // Retained as a supported public prop for API compatibility.
120
112
  // Device detection is UA-based; this value is not consumed internally.
@@ -124,20 +116,13 @@
124
116
  * @description
125
117
  * 📝 Component Local Interface
126
118
  */
127
- type IDeviceType =
128
- | 'desktop'
129
- | 'tablet'
130
- | 'mobile'
131
- ;
132
-
133
- $: $storeSession.isDarkTheme = isDarkTheme;
119
+ type IDeviceType = "desktop" | "tablet" | "mobile";
134
120
 
135
- let
136
- /**
121
+ let /**
137
122
  * @description
138
123
  * 📝 device type detected
139
124
  */
140
- deviceType: IDeviceType = 'desktop',
125
+ deviceType: IDeviceType = "desktop",
141
126
  /**
142
127
  * @description
143
128
  * 📝 geo-location response
@@ -147,24 +132,24 @@
147
132
  * @description
148
133
  * 📝 `Map` where, `key=ZoneId` and `value=HTMLElement`
149
134
  */
150
- mapBetarenaAdvertStandardElement = new Map < number, Element > (),
135
+ mapBetarenaAdvertStandardElement = new Map<number, Element>(),
151
136
  /**
152
137
  * @description
153
138
  * 📝 `Map` where, `key=CreativeId` and `value=AdvertObject` (temporary)
154
139
  */
155
- mapCreative = new Map < number, AdsCreativeMain > (),
140
+ mapCreative = new Map<number, AdsCreativeMain>(),
156
141
  /**
157
142
  * @description
158
143
  * 📝 Shared promise for one-time prerequisite initialization (device type + geolocation).
159
144
  * Set on first call to `ensureReady()`; subsequent callers await the same promise.
160
145
  */
161
- readinessPromise: Promise < void > | null = null,
146
+ readinessPromise: Promise<void> | null = null,
162
147
  /**
163
148
  * @description
164
149
  * 📝 ID of the deferred `initialize()` timeout, used to cancel it if the component
165
150
  * is destroyed before the delay elapses.
166
151
  */
167
- initTimeoutId: ReturnType < typeof setTimeout > | null = null,
152
+ initTimeoutId: ReturnType<typeof setTimeout> | null = null,
168
153
  /**
169
154
  * @description
170
155
  * 📝 Set to `true` once `onDestroy` fires, so any in-flight async work can bail out.
@@ -181,17 +166,12 @@
181
166
  * 📝 Previous value of `window.betarenaAdEngine` saved before this instance assigns it,
182
167
  * so it can be restored on destroy instead of unconditionally deleting.
183
168
  */
184
- prevWindowApi: unknown = undefined
185
- ;
186
-
187
- const
188
- /**
169
+ prevWindowApi: unknown = undefined;
170
+ const /**
189
171
  * @description
190
172
  * 📝 `List` of dynamically created advert components (to be destroyed on cleanup)
191
173
  */
192
- listAdWidgetElements: SvelteComponent[] = []
193
- ;
194
-
174
+ listAdWidgetElements: SvelteComponent[] = [];
195
175
  // #endregion ➤ 📌 VARIABLES
196
176
 
197
177
  // #region ➤ 🛠️ METHODS
@@ -215,40 +195,30 @@
215
195
  * 📝 `Map` generation for `HTMLElements`.
216
196
  * @returns { void }
217
197
  */
218
- function generateElementMap
219
- (
220
- rootElement?: ParentNode
221
- ): void
222
- {
198
+ function generateElementMap(rootElement?: ParentNode): void {
223
199
  mapBetarenaAdvertStandardElement.clear();
224
200
 
225
- const
226
- /**
201
+ const /**
227
202
  * @description
228
203
  * 📝 `List` of `HTMLElements` identified on `page` expecting a target `zone` advertisement injection.
229
204
  */
230
- listElementTarget = (rootElement ?? document).querySelectorAll('[data-betarena-zone-id]')
231
- ;
232
-
205
+ listElementTarget = (rootElement ?? document).querySelectorAll(
206
+ "[data-betarena-zone-id]",
207
+ );
233
208
  // ╭─────
234
209
  // │ NOTE:
235
210
  // │ |: querySelectorAll only returns descendants — if rootElement itself carries
236
211
  // │ |: the zone attribute it would be missed. Include it explicitly.
237
212
  // ╰─────
238
- if (rootElement instanceof Element && rootElement.hasAttribute('data-betarena-zone-id'))
239
- {
240
- const
241
- value = rootElement.getAttribute('data-betarena-zone-id')
242
- ;
243
-
244
- if (value)
245
- {
246
- for (const rawToken of value.split(','))
247
- {
248
- const
249
- token = rawToken.trim(),
250
- id = Number.parseInt(token, 10)
251
- ;
213
+ if (
214
+ rootElement instanceof Element &&
215
+ rootElement.hasAttribute("data-betarena-zone-id")
216
+ ) {
217
+ const value = rootElement.getAttribute("data-betarena-zone-id");
218
+ if (value) {
219
+ for (const rawToken of value.split(",")) {
220
+ const token = rawToken.trim(),
221
+ id = Number.parseInt(token, 10);
252
222
  if (!token || !Number.isFinite(id)) continue;
253
223
  mapBetarenaAdvertStandardElement.set(id, rootElement);
254
224
  }
@@ -256,59 +226,41 @@
256
226
  }
257
227
 
258
228
  // [🐞]
259
- logger
260
- (
261
- [
262
- '🚏 checkpoint ➤ generateElementMap(..) // START',
263
- `🔹 [var] ➤ listElementTarget.length ${listElementTarget.length}`
264
- ]
265
- );
229
+ logger([
230
+ "🚏 checkpoint ➤ generateElementMap(..) // START",
231
+ `🔹 [var] ➤ listElementTarget.length ${listElementTarget.length}`,
232
+ ]);
266
233
 
267
234
  // ╭─────
268
235
  // │ NOTE: |:| loop over elements detected as betarena elegible advert injection (global/outer)
269
236
  // ╰─────
270
- for (const elem of listElementTarget)
271
- {
272
- let
273
- /**
237
+ for (const elem of listElementTarget) {
238
+ let /**
274
239
  * @description
275
240
  * 📝 Value of Betarena Zone Id
276
241
  */
277
- value = elem.attributes.getNamedItem('data-betarena-zone-id')?.value
278
- ;
279
-
242
+ value = elem.attributes.getNamedItem("data-betarena-zone-id")?.value;
280
243
  if (!value) continue;
281
244
 
282
245
  // [🐞]
283
- logger
284
- (
285
- [
286
- `🔹 [var] ➤ value ${value}`
287
- ]
288
- );
246
+ logger([`🔹 [var] ➤ value ${value}`]);
289
247
 
290
- for (const rawToken of value.split(','))
291
- {
292
- const
293
- token = rawToken.trim(),
294
- id = Number.parseInt(token, 10)
295
- ;
248
+ for (const rawToken of value.split(",")) {
249
+ const token = rawToken.trim(),
250
+ id = Number.parseInt(token, 10);
296
251
  if (!token || !Number.isFinite(id)) continue;
297
252
  mapBetarenaAdvertStandardElement.set(id, elem);
298
253
  }
299
254
  }
300
255
 
301
256
  // [🐞]
302
- logger
303
- (
304
- [
305
- `🔹 [var]mapBetarenaAdvertStandardElement.keys ${[...mapBetarenaAdvertStandardElement.keys()]}`,
306
- `🔹 [var] ➤ mapBetarenaAdvertStandardElement.size ${mapBetarenaAdvertStandardElement.size}`,
307
- '🚏 checkpoint ➤ generateElementMap(..) // END'
308
- ]
309
- );
257
+ logger([
258
+ `🔹 [var] ➤ mapBetarenaAdvertStandardElement.keys ${[...mapBetarenaAdvertStandardElement.keys()]}`,
259
+ `🔹 [var] ➤ mapBetarenaAdvertStandardElement.size ${mapBetarenaAdvertStandardElement.size}`,
260
+ "🚏 checkpointgenerateElementMap(..) // END",
261
+ ]);
310
262
 
311
- return
263
+ return;
312
264
  }
313
265
 
314
266
  /**
@@ -322,97 +274,70 @@
322
274
  * 💠 **[required]** `list` of zone ids
323
275
  * @returns { Promise < void > }
324
276
  */
325
- async function injectBetarenaAds
326
- (
327
- opts: IAdsServiceData['request']['body'],
277
+ async function injectBetarenaAds(
278
+ opts: IAdsServiceData["request"]["body"],
328
279
  includesGlobalZone: boolean,
329
- refreshToken: number
330
- ): Promise < void >
331
- {
280
+ refreshToken: number,
281
+ ): Promise<void> {
332
282
  if (!document) return;
333
283
 
334
- const
335
- /**
284
+ const /**
336
285
  * @description
337
286
  * 📝 Response from `fetch`
338
287
  */
339
- dataRes0
340
- = await new ServiceAdEngine
341
- (
342
- betarenaEndpoint
343
- ).getAdEngineData
344
- (
345
- {
346
- query: {},
347
- body: opts
348
- }
349
- ),
288
+ dataRes0 = await new ServiceAdEngine(betarenaEndpoint).getAdEngineData({
289
+ query: {},
290
+ body: opts,
291
+ }),
350
292
  /**
351
293
  * @description
352
294
  * 📝 Response from `fetch`
353
295
  */
354
- dataRes1
355
- = await new ServiceAdEngine
356
- (
357
- betarenaEndpoint
358
- ).getAdEgnineTranslationData
359
- (
360
- {
361
- query: {
362
- language: strTranslationTarget
363
- },
364
- body: {}
365
- }
366
- )
367
- ;
368
-
296
+ dataRes1 = await new ServiceAdEngine(
297
+ betarenaEndpoint,
298
+ ).getAdEgnineTranslationData({
299
+ query: {
300
+ language: strTranslationTarget,
301
+ },
302
+ body: {},
303
+ });
369
304
  // [🐞]
370
- logger
371
- (
372
- [
373
- '🚏 checkpointinjectBetarenaAds(..) // START',
374
- `🔹 [var] ➤ dataRes0 ${JSON.stringify(dataRes0)}`,
375
- `🔹 [var] ➤ dataRes1 ${JSON.stringify(dataRes1)}`
376
- ]
377
- );
305
+ logger([
306
+ "🚏 checkpoint ➤ injectBetarenaAds(..) // START",
307
+ `🔹 [var] ➤ dataRes0 ${JSON.stringify(dataRes0)}`,
308
+ `🔹 [var]dataRes1 ${JSON.stringify(dataRes1)}`,
309
+ ]);
378
310
 
379
311
  if (isDestroyed || refreshToken !== refreshSerial) return;
380
312
 
381
- storeSession.updateData
382
- (
383
- [
384
- [
385
- 'setTranslation',
386
- dataRes1.success.data
387
- ]
388
- ]
389
- );
313
+ storeSession.updateData([["setTranslation", dataRes1.success.data]]);
390
314
 
391
315
  mapCreative = new Map(dataRes0.success.data.ads ?? []);
392
316
 
393
- const
394
- /**
317
+ const /**
395
318
  * @description
396
319
  * 📝 `Map` where, `key=ZoneId` and `value=listCampaignId`
397
320
  */
398
- mapZoneIdToCampaignId = new Map (dataRes0.success.data.mapZoneIdToCampaignId),
321
+ mapZoneIdToCampaignId = new Map(
322
+ dataRes0.success.data.mapZoneIdToCampaignId,
323
+ ),
399
324
  /**
400
325
  * @description
401
326
  * 📝 `Map` where, `key=CampaignId` and `value=listCreativeId`
402
327
  */
403
- mapCampaignIdToCreativeId = new Map (dataRes0.success.data.mapCampaignIdToCreativeId),
328
+ mapCampaignIdToCreativeId = new Map(
329
+ dataRes0.success.data.mapCampaignIdToCreativeId,
330
+ ),
404
331
  /**
405
332
  * @description
406
333
  * 📝 `Map` where, `key=ZoneId` and `value=listAuthorId`
407
334
  */
408
- mapZoneToAuthorIds = new Map (dataRes0.success.data.mapZoneIdToAuthorIds),
335
+ mapZoneToAuthorIds = new Map(dataRes0.success.data.mapZoneIdToAuthorIds),
409
336
  /**
410
337
  * @description
411
338
  * 📝 `Map` where, `key=ZoneId` and `value=listTagId`
412
339
  */
413
- mapZoneToTagIds = new Map (dataRes0.success.data.mapZoneIdToTagIds)
414
- ;
415
-
340
+ mapZoneToTagIds = new Map(dataRes0.success.data.mapZoneIdToTagIds);
416
341
  // ╭──────────────────────────────────────────────────────────────────────────────────╮
417
342
  // │ 💠 │ STEP :: ADVERT INJECTION │
418
343
  // ╰──────────────────────────────────────────────────────────────────────────────────╯
@@ -424,161 +349,118 @@
424
349
  // ┣─────
425
350
  // ┃ ZONE-ID :: 1,2,3,4
426
351
  // ╰─────
427
- for (const [zoneId, element] of mapBetarenaAdvertStandardElement)
428
- {
429
- const
430
- el = element as HTMLElement,
352
+ for (const [zoneId, element] of mapBetarenaAdvertStandardElement) {
353
+ const el = element as HTMLElement,
431
354
  parent = el.parentElement as HTMLElement | null,
432
- mountedAttr =
433
- [
355
+ mountedAttr = [
434
356
  el.dataset.betarenaAdMounted,
435
- parent?.dataset?.betarenaAdMounted
436
- ].filter(Boolean).join(','),
437
- mountedZones = mountedAttr.split(',').map(z => z.trim()).filter(Boolean)
438
- ;
357
+ parent?.dataset?.betarenaAdMounted,
358
+ ]
359
+ .filter(Boolean)
360
+ .join(","),
361
+ mountedZones = mountedAttr
362
+ .split(",")
363
+ .map((z) => z.trim())
364
+ .filter(Boolean);
439
365
  if (mountedZones.includes(String(zoneId))) continue;
440
366
 
441
- const
442
- /**
367
+ const /**
443
368
  * @description
444
369
  * 📝 **campaign ids** of respective **zone id**
445
370
  */
446
- campaignIds
447
- = (mapZoneIdToCampaignId.get(zoneId) ?? []),
371
+ campaignIds = mapZoneIdToCampaignId.get(zoneId) ?? [],
448
372
  /**
449
373
  * @description
450
374
  * 📝 `List` of creative data point(s)
451
375
  */
452
- creativeAdData
453
- = removeNull
454
- (
455
- campaignIds
456
- // ╭─────
457
- // │ NOTE:
458
- // |: Filter out `campaign ids` that have no `creative ids`
459
- // ╰─────
460
- .filter
461
- (
462
- x =>
463
- {
464
- if (mapCampaignIdToCreativeId.has(x))
465
- return true;
466
- ;
467
- return false;
468
- }
469
- )
376
+ creativeAdData = removeNull(
377
+ campaignIds
378
+ // ╭─────
379
+ // │ NOTE:
380
+ // │ |: Filter out `campaign ids` that have no `creative ids`
381
+ // ╰─────
382
+ .filter((x) => {
383
+ if (mapCampaignIdToCreativeId.has(x)) return true;
384
+ return false;
385
+ })
386
+ // ╭─────
387
+ // │ NOTE:
388
+ // │ |: Map over `campaign ids` and return `creative data`
389
+ // ╰─────
390
+ .map((x) => {
470
391
  // ╭─────
471
392
  // │ NOTE:
472
- // │ |: Map over `campaign ids` and return `creative data`
393
+ // │ |: Loop over creative data and inject adverts
473
394
  // ╰─────
474
- .map
475
- (
476
- x =>
477
- {
478
- // ╭─────
479
- // NOTE:
480
- // │ |: Loop over creative data and inject adverts
481
- // ╰─────
482
- for (const creativeId of mapCampaignIdToCreativeId.get(x)!)
483
- {
484
- if (mapCreative.has(creativeId))
485
- return mapCreative.get(creativeId);
486
- ;
487
- }
488
- }
489
- )
490
- ) as AdsCreativeMain[]
491
- ;
492
-
395
+ for (const creativeId of mapCampaignIdToCreativeId.get(x)!) {
396
+ if (mapCreative.has(creativeId))
397
+ return mapCreative.get(creativeId);
398
+ }
399
+ }),
400
+ ) as AdsCreativeMain[];
493
401
  // [🐞]
494
- logger
495
- (
496
- [
497
- `🔹 [var] ➤ creativeAdData.length ${creativeAdData.length}`
498
- ]
499
- );
402
+ logger([`🔹 [var] ➤ creativeAdData.length ${creativeAdData.length}`]);
500
403
 
501
404
  // ╭─────
502
405
  // │ NOTE:
503
406
  // │ |: Loop over creative data and inject adverts, based on respective `zoneId`.
504
407
  // ╰─────
505
- if (zoneId == 1)
506
- {
507
- for (const adData of (creativeAdData ?? []))
508
- new WidgetAdGeneral
509
- (
510
- {
511
- target: element,
512
- props:
513
- {
514
- adData
515
- }
516
- }
517
- );
518
- ;
519
- if (creativeAdData.length > 0)
520
- {
408
+ if (zoneId == 1) {
409
+ for (const adData of creativeAdData ?? [])
410
+ new WidgetAdGeneral({
411
+ target: element,
412
+ props: {
413
+ adData,
414
+ },
415
+ });
416
+ if (creativeAdData.length > 0) {
521
417
  mountedZones.push(String(zoneId));
522
- el.dataset.betarenaAdMounted = mountedZones.join(',');
418
+ el.dataset.betarenaAdMounted = mountedZones.join(",");
523
419
  }
524
- }
525
- else if (zoneId == 2 || zoneId == 3)
526
- {
527
- for (const adData of (creativeAdData ?? []))
528
- new AdvertInterScrollerChild
529
- (
530
- {
531
- target: element,
532
- props:
533
- {
534
- instanceNode: element,
535
- objectAdvertData: adData
536
- }
537
- }
538
- );
539
- ;
540
- if (creativeAdData.length > 0)
541
- {
420
+ } else if (zoneId == 2 || zoneId == 3) {
421
+ for (const adData of creativeAdData ?? [])
422
+ new AdvertInterScrollerChild({
423
+ target: element,
424
+ props: {
425
+ instanceNode: element,
426
+ objectAdvertData: adData,
427
+ },
428
+ });
429
+ if (creativeAdData.length > 0) {
542
430
  mountedZones.push(String(zoneId));
543
- el.dataset.betarenaAdMounted = mountedZones.join(',');
431
+ el.dataset.betarenaAdMounted = mountedZones.join(",");
544
432
  }
545
- }
546
- else if (zoneId == 4)
547
- {
433
+ } else if (zoneId == 4) {
548
434
  if (!element.parentElement) continue;
549
435
 
550
- for (const adData of (creativeAdData ?? []))
551
- new AdvertLeftSide
552
- (
553
- {
554
- target: element.parentElement,
555
- props:
556
- {
557
- objectAdvertData: adData
558
- }
559
- }
560
- );
561
- ;
562
- if (creativeAdData.length > 0)
563
- {
436
+ for (const adData of creativeAdData ?? [])
437
+ new AdvertLeftSide({
438
+ target: element.parentElement,
439
+ props: {
440
+ objectAdvertData: adData,
441
+ },
442
+ });
443
+ if (creativeAdData.length > 0) {
564
444
  mountedZones.push(String(zoneId));
565
- el.dataset.betarenaAdMounted = mountedZones.join(',');
445
+ el.dataset.betarenaAdMounted = mountedZones.join(",");
566
446
  // ╭─────
567
447
  // │ NOTE:
568
448
  // │ |: Also mark the actual injection target (parentElement) so that
569
449
  // │ |: if the zone element is replaced while the parent persists,
570
450
  // │ |: subsequent refreshAds() calls won't re-inject into the same parent.
571
451
  // ╰─────
572
- if (element.parentElement)
573
- {
574
- const
575
- parentMountedRaw = (element.parentElement as HTMLElement).dataset.betarenaAdMounted ?? '',
576
- parentMountedZones = parentMountedRaw.split(',').map(z => z.trim()).filter(Boolean)
577
- ;
578
- if (!parentMountedZones.includes(String(zoneId)))
579
- {
452
+ if (element.parentElement) {
453
+ const parentMountedRaw =
454
+ (element.parentElement as HTMLElement).dataset
455
+ .betarenaAdMounted ?? "",
456
+ parentMountedZones = parentMountedRaw
457
+ .split(",")
458
+ .map((z) => z.trim())
459
+ .filter(Boolean);
460
+ if (!parentMountedZones.includes(String(zoneId))) {
580
461
  parentMountedZones.push(String(zoneId));
581
- (element.parentElement as HTMLElement).dataset.betarenaAdMounted = parentMountedZones.join(',');
462
+ (element.parentElement as HTMLElement).dataset.betarenaAdMounted =
463
+ parentMountedZones.join(",");
582
464
  }
583
465
  }
584
466
  }
@@ -594,87 +476,66 @@
594
476
  // │ |: (not from DOM-filtered targetZoneIds) so global placements inject correctly
595
477
  // │ |: even when no zone-1 element exists in the DOM.
596
478
  // ╰─────
597
- if (includesGlobalZone && isArticlePage && (authorId || authorArticleTagIds.length > 0))
598
- {
479
+ if (
480
+ includesGlobalZone &&
481
+ isArticlePage &&
482
+ (authorId || authorArticleTagIds.length > 0)
483
+ ) {
599
484
  // ╭─────
600
485
  // │ NOTE:
601
486
  // │ |: loop over creative data AND inject adverts, based on 'authorId'
602
487
  // ╰─────
603
- for (const [zoneId, authorIds] of mapZoneToAuthorIds)
604
- {
605
- if (!authorId || (authorIds.length > 0 && !authorIds.includes(authorId)))
488
+ for (const [zoneId, authorIds] of mapZoneToAuthorIds) {
489
+ if (
490
+ !authorId ||
491
+ (authorIds.length > 0 && !authorIds.includes(authorId))
492
+ )
606
493
  continue;
607
- ;
608
-
609
- const
610
- /**
494
+ const /**
611
495
  * @description
612
496
  * 📝 **campaign ids** of respective **zone id**
613
497
  */
614
- campaignIds
615
- = (mapZoneIdToCampaignId.get(zoneId) ?? []),
498
+ campaignIds = mapZoneIdToCampaignId.get(zoneId) ?? [],
616
499
  /**
617
500
  * @description
618
501
  * 📝 `List` of creative data point(s)
619
502
  */
620
- creativeAdData
621
- = removeNull
622
- (
623
- campaignIds.map
624
- (
625
- x =>
626
- {
627
- if (mapCampaignIdToCreativeId.has(x))
628
- {
629
- const
630
- /**
631
- * @description
632
- * 📝 **creative ids**
633
- */
634
- creativeIds = mapCampaignIdToCreativeId.get(x)!
635
- ;
636
-
637
- for (const creativeId of creativeIds)
638
- {
639
- if (mapCreative.has(creativeId))
640
- return mapCreative.get(creativeId);
641
- ;
642
- }
643
- }
503
+ creativeAdData = removeNull(
504
+ campaignIds.map((x) => {
505
+ if (mapCampaignIdToCreativeId.has(x)) {
506
+ const /**
507
+ * @description
508
+ * 📝 **creative ids**
509
+ */
510
+ creativeIds = mapCampaignIdToCreativeId.get(x)!;
511
+ for (const creativeId of creativeIds) {
512
+ if (mapCreative.has(creativeId))
513
+ return mapCreative.get(creativeId);
644
514
  }
645
- )
646
- ) as AdsCreativeMain[]
647
- ;
648
-
515
+ }
516
+ }),
517
+ ) as AdsCreativeMain[];
649
518
  // [🐞]
650
- logger
651
- (
652
- [
653
- '🚏 checkpoint ➤ injectBetarenaAds(..) // CHECKPOINT-1',
654
- `🔹 [var] ➤ creativeAdData ${creativeAdData.length}`
655
- ]
656
- );
519
+ logger([
520
+ "🚏 checkpoint ➤ injectBetarenaAds(..) // CHECKPOINT-1",
521
+ `🔹 [var] ➤ creativeAdData ${creativeAdData.length}`,
522
+ ]);
657
523
 
658
524
  // ╭─────
659
525
  // │ NOTE:
660
526
  // │ |: Loop over creative data and inject adverts
661
527
  // ╰─────
662
- for (const adData of creativeAdData)
663
- {
528
+ for (const adData of creativeAdData) {
664
529
  if (zoneId != 1) continue;
665
530
 
666
- listAdWidgetElements.push
667
- (
668
- new WidgetAdvertSlide
669
- (
670
- {
671
- target: document.body,
672
- props:
673
- {
674
- adData
675
- }
676
- }
677
- )
531
+ listAdWidgetElements.push(
532
+ new WidgetAdvertSlide({
533
+ target: document.body,
534
+ props: {
535
+ adData,
536
+ isLoggedIn,
537
+ },
538
+ }),
678
539
  );
679
540
  }
680
541
  }
@@ -683,81 +544,59 @@
683
544
  // │ NOTE:
684
545
  // │ |: loop over creative data AND inject adverts, based on 'tagIds'
685
546
  // ╰─────
686
- for (const [zoneId, tagIds] of mapZoneToTagIds)
687
- {
688
- if (tagIds.length > 0 && authorArticleTagIds.filter(x => { return tagIds.includes(x) } ).length == 0)
547
+ for (const [zoneId, tagIds] of mapZoneToTagIds) {
548
+ if (
549
+ tagIds.length > 0 &&
550
+ authorArticleTagIds.filter((x) => {
551
+ return tagIds.includes(x);
552
+ }).length == 0
553
+ )
689
554
  continue;
690
- ;
691
-
692
- const
693
- /**
555
+ const /**
694
556
  * @description
695
557
  * 📝 **campaign ids** of respective **zone id**
696
558
  */
697
- campaignIds
698
- = (mapZoneIdToCampaignId.get(zoneId) ?? []),
559
+ campaignIds = mapZoneIdToCampaignId.get(zoneId) ?? [],
699
560
  /**
700
561
  * @description
701
562
  * 📝 `List` of creative data point(s)
702
563
  */
703
- creativeAdData
704
- = removeNull
705
- (
706
- campaignIds.map
707
- (
708
- x =>
709
- {
710
- if (mapCampaignIdToCreativeId.has(x))
711
- {
712
- const
713
- /**
714
- * @description
715
- * 📝 **creative ids**
716
- */
717
- creativeIds = mapCampaignIdToCreativeId.get(x)!
718
- ;
719
-
720
- for (const creativeId of creativeIds)
721
- {
722
- if (mapCreative.has(creativeId))
723
- return mapCreative.get(creativeId);
724
- ;
725
- }
726
- }
564
+ creativeAdData = removeNull(
565
+ campaignIds.map((x) => {
566
+ if (mapCampaignIdToCreativeId.has(x)) {
567
+ const /**
568
+ * @description
569
+ * 📝 **creative ids**
570
+ */
571
+ creativeIds = mapCampaignIdToCreativeId.get(x)!;
572
+ for (const creativeId of creativeIds) {
573
+ if (mapCreative.has(creativeId))
574
+ return mapCreative.get(creativeId);
727
575
  }
728
- )
729
- ) as AdsCreativeMain[]
730
- ;
731
-
576
+ }
577
+ }),
578
+ ) as AdsCreativeMain[];
732
579
  // [🐞]
733
- logger
734
- (
735
- [
736
- '🚏 checkpoint ➤ injectBetarenaAds(..) // CHECKPOINT-2',
737
- `🔹 [var] ➤ creativeAdData ${creativeAdData.length}`
738
- ]
739
- );
580
+ logger([
581
+ "🚏 checkpoint ➤ injectBetarenaAds(..) // CHECKPOINT-2",
582
+ `🔹 [var] ➤ creativeAdData ${creativeAdData.length}`,
583
+ ]);
740
584
 
741
585
  // ╭─────
742
586
  // │ NOTE:
743
587
  // │ |: Loop over creative data and inject adverts
744
588
  // ╰─────
745
- for (const adData of creativeAdData)
746
- {
589
+ for (const adData of creativeAdData) {
747
590
  if (zoneId != 1) continue;
748
591
 
749
- listAdWidgetElements.push
750
- (
751
- new WidgetAdvertSlide
752
- (
753
- {
754
- target: document.body,
755
- props:
756
- {
757
- adData
758
- }
759
- }
760
- )
592
+ listAdWidgetElements.push(
593
+ new WidgetAdvertSlide({
594
+ target: document.body,
595
+ props: {
596
+ adData,
597
+ isLoggedIn,
598
+ },
599
+ }),
761
600
  );
762
601
  }
763
602
  }
@@ -771,16 +610,12 @@
771
610
  // │ |: Inject STANDARD SLIDER adverts in 'document.body'
772
611
  // │ |: Guard with `includesGlobalZone` to avoid body injections on scoped refreshes.
773
612
  // ╰─────
774
- else if (includesGlobalZone && isArticlePage)
775
- {
613
+ else if (includesGlobalZone && isArticlePage) {
776
614
  // [🐞]
777
- logger
778
- (
779
- [
780
- '🚏 checkpoint ➤ injectBetarenaAds(..) // CHECKPOINT-3',
781
- `🔹 [var] ➤ mapCreative.length ${mapCreative.size}`
782
- ]
783
- );
615
+ logger([
616
+ "🚏 checkpoint ➤ injectBetarenaAds(..) // CHECKPOINT-3",
617
+ `🔹 [var] ➤ mapCreative.length ${mapCreative.size}`,
618
+ ]);
784
619
 
785
620
  // ╭─────
786
621
  // │ NOTE:
@@ -788,34 +623,24 @@
788
623
  // │ |: Build a Set of creative IDs that belong to zone 1 so we only
789
624
  // │ |: inject zone-1-specific creatives rather than all slider-type creatives.
790
625
  // ╰─────
791
- const
792
- zone1CampaignIds
793
- = mapZoneIdToCampaignId.get(1) ?? [],
794
- zone1CreativeIds
795
- = new Set
796
- (
797
- zone1CampaignIds
798
- .flatMap(campaignId => mapCampaignIdToCreativeId.get(campaignId) ?? [])
799
- )
800
- ;
801
-
802
- for (const [creativeId, adData] of mapCreative)
803
- {
626
+ const zone1CampaignIds = mapZoneIdToCampaignId.get(1) ?? [],
627
+ zone1CreativeIds = new Set(
628
+ zone1CampaignIds.flatMap(
629
+ (campaignId) => mapCampaignIdToCreativeId.get(campaignId) ?? [],
630
+ ),
631
+ );
632
+ for (const [creativeId, adData] of mapCreative) {
804
633
  if (adData.type != 1) continue;
805
634
  if (!zone1CreativeIds.has(creativeId)) continue;
806
635
 
807
- listAdWidgetElements.push
808
- (
809
- new WidgetAdvertSlide
810
- (
811
- {
812
- target: document.body,
813
- props:
814
- {
815
- adData
816
- }
817
- }
818
- )
636
+ listAdWidgetElements.push(
637
+ new WidgetAdvertSlide({
638
+ target: document.body,
639
+ props: {
640
+ adData,
641
+ isLoggedIn,
642
+ },
643
+ }),
819
644
  );
820
645
  }
821
646
  }
@@ -834,28 +659,18 @@
834
659
  * await the same in-flight promise.
835
660
  * @returns { Promise < void > }
836
661
  */
837
- async function ensureReady
838
- (
839
- ): Promise < void >
840
- {
662
+ async function ensureReady(): Promise<void> {
841
663
  if (readinessPromise) return readinessPromise;
842
664
 
843
- readinessPromise =
844
- (
845
- async () =>
846
- {
847
- try
848
- {
849
- deviceType = detectDeviceWithUA() as IDeviceType;
850
- geoLocation = await getUserLocation();
851
- }
852
- catch (e)
853
- {
854
- readinessPromise = null;
855
- throw e;
856
- }
665
+ readinessPromise = (async () => {
666
+ try {
667
+ deviceType = detectDeviceWithUA() as IDeviceType;
668
+ geoLocation = await getUserLocation();
669
+ } catch (e) {
670
+ readinessPromise = null;
671
+ throw e;
857
672
  }
858
- )();
673
+ })();
859
674
 
860
675
  return readinessPromise;
861
676
  }
@@ -874,39 +689,26 @@
874
689
  * 💠 Optional scope: restrict to specific zone IDs and / or a DOM subtree.
875
690
  * @returns { Promise < void > }
876
691
  */
877
- async function refreshAds
878
- (
879
- opts?:
880
- {
881
- zoneIds?: number[];
882
- rootElement?: HTMLElement;
883
- }
884
- ): Promise < void >
885
- {
692
+ async function refreshAds(opts?: {
693
+ zoneIds?: number[];
694
+ rootElement?: HTMLElement;
695
+ }): Promise<void> {
886
696
  if (isDestroyed) return;
887
697
 
888
- const
889
- mySerial = ++refreshSerial
890
- ;
891
-
698
+ const mySerial = ++refreshSerial;
892
699
  await ensureReady();
893
700
 
894
701
  if (isDestroyed || mySerial !== refreshSerial) return;
895
702
 
896
703
  generateElementMap(opts?.rootElement);
897
704
 
898
- let
899
- /**
705
+ let /**
900
706
  * @description
901
707
  * 📝 Zone IDs discovered in the current DOM scan (or filtered subset).
902
708
  */
903
- targetZoneIds = [...mapBetarenaAdvertStandardElement.keys()]
904
- ;
905
-
709
+ targetZoneIds = [...mapBetarenaAdvertStandardElement.keys()];
906
710
  if (opts?.zoneIds && opts.zoneIds.length > 0)
907
- targetZoneIds = targetZoneIds.filter(id => opts.zoneIds!.includes(id));
908
- ;
909
-
711
+ targetZoneIds = targetZoneIds.filter((id) => opts.zoneIds!.includes(id));
910
712
  // ╭─────
911
713
  // │ NOTE:
912
714
  // │ |: Zone 1 (global slider/popup) has no DOM element and will never appear
@@ -916,16 +718,10 @@
916
718
  // │ |: article pages; creative-level targeting handles device/country/author filtering.
917
719
  // │ |: Respect scoped refreshes: only add zone 1 if the caller's scope includes it.
918
720
  // ╰─────
919
- const
920
- shouldRequestZone1
921
- = isArticlePage
922
- && (!opts?.zoneIds || opts.zoneIds.length === 0 || opts.zoneIds.includes(1))
923
- ;
924
-
925
- if (shouldRequestZone1 && !targetZoneIds.includes(1))
926
- targetZoneIds.push(1);
927
- ;
928
-
721
+ const shouldRequestZone1 =
722
+ isArticlePage &&
723
+ (!opts?.zoneIds || opts.zoneIds.length === 0 || opts.zoneIds.includes(1));
724
+ if (shouldRequestZone1 && !targetZoneIds.includes(1)) targetZoneIds.push(1);
929
725
  // ╭─────
930
726
  // │ NOTE:
931
727
  // │ |: Only tear down previously injected global/body components when the
@@ -934,22 +730,22 @@
934
730
  // │ |: Scoped refreshes that exclude zone 1 leave existing body-mounted
935
731
  // │ |: widgets untouched to avoid permanently removing them.
936
732
  // ╰─────
937
- if (!opts?.zoneIds || opts.zoneIds.length === 0 || opts.zoneIds.includes(1))
938
- {
939
- for (const item of listAdWidgetElements)
940
- item.$destroy();
941
- ;
733
+ if (
734
+ !opts?.zoneIds ||
735
+ opts.zoneIds.length === 0 ||
736
+ opts.zoneIds.includes(1)
737
+ ) {
738
+ for (const item of listAdWidgetElements) item.$destroy();
942
739
  listAdWidgetElements.length = 0;
943
740
  }
944
741
 
945
- await injectBetarenaAds
946
- (
742
+ await injectBetarenaAds(
947
743
  {
948
744
  deviceType,
949
- isoCountryCode: geoLocation?.country_code ?? 'EN',
745
+ isoCountryCode: geoLocation?.country_code ?? "EN",
950
746
  authorId: authorId ?? undefined,
951
747
  tagIds: authorArticleTagIds,
952
- zoneIds: targetZoneIds
748
+ zoneIds: targetZoneIds,
953
749
  },
954
750
  // ╭─────
955
751
  // │ NOTE:
@@ -958,7 +754,7 @@
958
754
  // │ |: won't include 1, but a full/zone-1 refresh should still inject global placements.
959
755
  // ╰─────
960
756
  !opts?.zoneIds || opts.zoneIds.length === 0 || opts.zoneIds.includes(1),
961
- mySerial
757
+ mySerial,
962
758
  );
963
759
 
964
760
  return;
@@ -973,17 +769,9 @@
973
769
  * 📝 Logic bundle for advert initialization
974
770
  * @returns { Promise < void > }
975
771
  */
976
- async function initialize
977
- (
978
- ): Promise < void >
979
- {
772
+ async function initialize(): Promise<void> {
980
773
  // [🐞]
981
- logger
982
- (
983
- [
984
- '🚏 checkpoint ➤ initialize(..) // START'
985
- ]
986
- );
774
+ logger(["🚏 checkpoint ➤ initialize(..) // START"]);
987
775
 
988
776
  await refreshAds();
989
777
 
@@ -1001,117 +789,71 @@
1001
789
  // │ as soon as 'this' .svelte file is ran. │
1002
790
  // ╰────────────────────────────────────────────────────────────────────────╯
1003
791
 
1004
- onMount
1005
- (
1006
- async () =>
1007
- {
1008
- betarenaAdEngineStore.useLocalStorage();
1009
- // ╭─────
1010
- // │ NOTE:
1011
- // │ |: Expose public API immediately so host apps can call refreshAds()
1012
- // │ |: without waiting for the delayed initialize().
1013
- // |: refreshAds() self-initializes prerequisites via ensureReady() if needed.
1014
- // |: Save any existing value so it can be restored when this instance is destroyed.
1015
- // ╰─────
1016
- prevWindowApi = (window as any).betarenaAdEngine;
1017
- (window as any).betarenaAdEngine = { refreshAds };
1018
- // ╭─────
1019
- // NOTE:
1020
- // |: Delay initialization to ensure all elements are loaded on page
1021
- // ╰─────
1022
- initTimeoutId = setTimeout
1023
- (
1024
- () =>
1025
- {
1026
- initialize().catch
1027
- (
1028
- (error) =>
1029
- {
1030
- logger
1031
- (
1032
- [
1033
- '🚨 checkpoint ➤ initialize(..) // UNHANDLED ERROR',
1034
- `🔹 [var] ➤ error ${error}`
1035
- ]
1036
- );
1037
- }
1038
- );
1039
- },
1040
- 1000
1041
- );
1042
- return;
1043
- }
1044
- );
1045
-
1046
- onDestroy
1047
- (
1048
- () =>
1049
- {
1050
- // [🐞]
1051
- logger
1052
- (
1053
- [
1054
- 'Betarena Ad-Engine ⏰ onMount ⏰ cleanup',
1055
- `🔹 [var] ➤ listAdWidgetElements.length ${listAdWidgetElements.length}`
1056
- ],
1057
- );
1058
-
1059
- isDestroyed = true;
1060
-
1061
- if (initTimeoutId !== null)
1062
- clearTimeout(initTimeoutId);
1063
- ;
792
+ onMount(async () => {
793
+ betarenaAdEngineStore.useLocalStorage();
794
+ // ╭─────
795
+ // │ NOTE:
796
+ // │ |: Expose public API immediately so host apps can call refreshAds()
797
+ // │ |: without waiting for the delayed initialize().
798
+ // │ |: refreshAds() self-initializes prerequisites via ensureReady() if needed.
799
+ // │ |: Save any existing value so it can be restored when this instance is destroyed.
800
+ // ╰─────
801
+ prevWindowApi = (window as any).betarenaAdEngine;
802
+ (window as any).betarenaAdEngine = { refreshAds };
803
+ // ╭─────
804
+ // NOTE:
805
+ // |: Delay initialization to ensure all elements are loaded on page
806
+ // ╰─────
807
+ initTimeoutId = setTimeout(() => {
808
+ initialize().catch((error) => {
809
+ logger([
810
+ "🚨 checkpoint ➤ initialize(..) // UNHANDLED ERROR",
811
+ `🔹 [var] ➤ error ${error}`,
812
+ ]);
813
+ });
814
+ }, 1000);
815
+ return;
816
+ });
1064
817
 
1065
- // ╭─────
1066
- // │ NOTE:
1067
- // │ |: Only remove/restore the global if this instance still owns it.
1068
- // │ |: A later-mounted instance or host code may have overwritten it.
1069
- // ╰─────
1070
- if ((window as any).betarenaAdEngine?.refreshAds === refreshAds)
1071
- {
1072
- if (prevWindowApi !== undefined)
1073
- (window as any).betarenaAdEngine = prevWindowApi;
1074
- else
1075
- delete (window as any).betarenaAdEngine;
1076
- }
818
+ onDestroy(() => {
819
+ // [🐞]
820
+ logger([
821
+ "Betarena Ad-Engine onMount cleanup",
822
+ `🔹 [var] ➤ listAdWidgetElements.length ${listAdWidgetElements.length}`,
823
+ ]);
1077
824
 
1078
- // ╭─────
1079
- // │ NOTE:
1080
- // │ |: Destroy all dynamically created advert components
1081
- // ╰─────
1082
- for (const item of listAdWidgetElements)
1083
- item.$destroy();
1084
- ;
825
+ isDestroyed = true;
1085
826
 
1086
- return;
827
+ if (initTimeoutId !== null) clearTimeout(initTimeoutId);
828
+ // ╭─────
829
+ // │ NOTE:
830
+ // │ |: Only remove/restore the global if this instance still owns it.
831
+ // │ |: A later-mounted instance or host code may have overwritten it.
832
+ // ╰─────
833
+ if ((window as any).betarenaAdEngine?.refreshAds === refreshAds) {
834
+ if (prevWindowApi !== undefined)
835
+ (window as any).betarenaAdEngine = prevWindowApi;
836
+ else delete (window as any).betarenaAdEngine;
1087
837
  }
1088
- );
1089
838
 
1090
- // #endregion ➤ 🔄 LIFECYCLE [SVELTE]
839
+ // ╭─────
840
+ // │ NOTE:
841
+ // │ |: Destroy all dynamically created advert components
842
+ // ╰─────
843
+ for (const item of listAdWidgetElements) item.$destroy();
844
+ return;
845
+ });
1091
846
 
847
+ // #endregion ➤ 🔄 LIFECYCLE [SVELTE]
1092
848
  </script>
1093
849
 
1094
850
  <svelte:window
1095
- on:resize=
1096
- {
1097
- () =>
1098
- {
1099
- detectDeviceWithUA();
1100
- return;
1101
- }
1102
- }
851
+ on:resize={() => {
852
+ detectDeviceWithUA();
853
+ return;
854
+ }}
1103
855
  />
1104
856
 
1105
- <svelte:head>
1106
-
1107
- {#if isStandalone}
1108
- <link rel="preconnect" href="https://fonts.googleapis.com">
1109
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
1110
- <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
1111
- {/if}
1112
-
1113
- </svelte:head>
1114
-
1115
857
  <!--
1116
858
  ╭──────────────────────────────────────────────────────────────────────────────────╮
1117
859
  │ 💠 Svelte Component HTML │
@@ -1162,7 +904,7 @@
1162
904
  >
1163
905
  </div> -->
1164
906
 
1165
- <hr>
907
+ <hr />
1166
908
 
1167
909
  <!-- <div
1168
910
  data-betarena-zone-id=2,3
@@ -1174,10 +916,7 @@
1174
916
  {/each}
1175
917
  </div> -->
1176
918
 
1177
- <div
1178
- data-betarena-zone-id=4
1179
- >
1180
- </div>
919
+ <div data-betarena-zone-id="4"></div>
1181
920
  {/if}
1182
921
 
1183
922
  <!--
@@ -1190,20 +929,3 @@
1190
929
  ╰──────────────────────────────────────────────────────────────────────────────────╯
1191
930
  -->
1192
931
 
1193
- <style lang="scss">
1194
-
1195
- :global
1196
- {
1197
- // ╭─────
1198
- // │ NOTE:
1199
- // │ |: Use purged styles for production build
1200
- // ╰─────
1201
- @import '../style/app.purged.min.scss';
1202
- // ╭─────
1203
- // │ NOTE:
1204
- // │ |: Import main styles (uncomment for development)
1205
- // ╰─────
1206
- // @import '../style/app.scss';
1207
- }
1208
-
1209
- </style>