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