@betarena/ad-engine 0.0.6 → 0.0.8
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.
- package/.eslintrc.yaml +164 -0
- package/dist/icon-close.svg +9 -0
- package/dist/index.css +1 -0
- package/dist/index.html +2 -1
- package/dist/index.js +11 -0
- package/dist/roboto-cyrillic-400-normal.woff +0 -0
- package/dist/roboto-cyrillic-400-normal.woff2 +0 -0
- package/dist/roboto-cyrillic-ext-400-normal.woff +0 -0
- package/dist/roboto-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/roboto-greek-400-normal.woff +0 -0
- package/dist/roboto-greek-400-normal.woff2 +0 -0
- package/dist/roboto-latin-400-normal.woff +0 -0
- package/dist/roboto-latin-400-normal.woff2 +0 -0
- package/dist/roboto-latin-ext-400-normal.woff +0 -0
- package/dist/roboto-latin-ext-400-normal.woff2 +0 -0
- package/dist/roboto-vietnamese-400-normal.woff +0 -0
- package/dist/roboto-vietnamese-400-normal.woff2 +0 -0
- package/package.json +34 -14
- package/src/App.svelte +8 -0
- package/src/lib/Widget-AdEngine.svelte +492 -0
- package/src/lib/Widget-AdGeneral.svelte +284 -0
- package/src/lib/Widget-AdvertSlide.svelte +384 -0
- package/src/lib/assets/icon-close.svg +9 -0
- package/src/lib/assets/icon-external-link.svg +4 -0
- package/src/lib/constants/instance.ts +10 -0
- package/src/lib/store.ts +284 -0
- package/src/lib/types/ad-engine.d.ts +21 -0
- package/src/lib/types/geojs.d.ts +83 -0
- package/src/lib/types/global.d.ts +4 -0
- package/src/lib/types/vite-env.d.ts +2 -0
- package/src/lib/utils/device.ts +37 -0
- package/src/lib/utils/fetch.ts +107 -0
- package/src/lib/utils/geo.ts +34 -0
- package/src/main.ts +17 -0
- package/vite.config.ts +29 -3
- package/dist/assets/index-Cm9IxoHj.js +0 -24
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
╭──────────────────────────────────────────────────────────────────────────────────╮
|
|
3
|
+
│ 📌 High Order Component Overview │
|
|
4
|
+
┣──────────────────────────────────────────────────────────────────────────────────┫
|
|
5
|
+
│ ➤ Internal Svelte Code Format :|: V.8.0 │
|
|
6
|
+
│ ➤ Status :|: 🔒 LOCKED │
|
|
7
|
+
│ ➤ Author(s) :|: @migbash │
|
|
8
|
+
┣──────────────────────────────────────────────────────────────────────────────────┫
|
|
9
|
+
│ 📝 Description │
|
|
10
|
+
┣──────────────────────────────────────────────────────────────────────────────────┫
|
|
11
|
+
│ Betarena Ad-Engine Component │
|
|
12
|
+
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
<!--
|
|
16
|
+
╭──────────────────────────────────────────────────────────────────────────────────╮
|
|
17
|
+
│ 🟦 Svelte Component JS/TS │
|
|
18
|
+
┣──────────────────────────────────────────────────────────────────────────────────┫
|
|
19
|
+
│ ➤ HINT: │ Access snippets for '<script> [..] </script>' those found in │
|
|
20
|
+
│ │ '.vscode/snippets.code-snippets' via intellisense using 'doc' │
|
|
21
|
+
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
22
|
+
-->
|
|
23
|
+
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
|
|
26
|
+
// #region ➤ 📦 Package Imports
|
|
27
|
+
|
|
28
|
+
// ╭────────────────────────────────────────────────────────────────────────╮
|
|
29
|
+
// │ NOTE: │
|
|
30
|
+
// │ Please add inside 'this' region the 'imports' that are required │
|
|
31
|
+
// │ by 'this' .svelte file is ran. │
|
|
32
|
+
// │ IMPORTANT │
|
|
33
|
+
// │ Please, structure the imports as follows: │
|
|
34
|
+
// │ 1. svelte/sveltekit imports │
|
|
35
|
+
// │ 2. project-internal files and logic │
|
|
36
|
+
// │ 3. component import(s) │
|
|
37
|
+
// │ 4. assets import(s) │
|
|
38
|
+
// │ 5. type(s) imports(s) │
|
|
39
|
+
// ╰────────────────────────────────────────────────────────────────────────╯
|
|
40
|
+
|
|
41
|
+
import { onMount } from 'svelte';
|
|
42
|
+
|
|
43
|
+
import { betarenaEndpoint } from './constants/instance.js';
|
|
44
|
+
import { betarenaAdEngineStore } from './store.js';
|
|
45
|
+
import { postMod } from './utils/fetch.js';
|
|
46
|
+
import { detectDeviceType } from './utils/device.js';
|
|
47
|
+
import { getUserLocation } from './utils/geo.js';
|
|
48
|
+
|
|
49
|
+
import WidgetAdvertSlide from './Widget-AdvertSlide.svelte';
|
|
50
|
+
import WidgetAdGeneral from './Widget-AdGeneral.svelte';
|
|
51
|
+
|
|
52
|
+
import type { IAdsRequestBody, IAdsResponseBody } from '@betarena/scores-lib/types/ad-engine/index.js';
|
|
53
|
+
import type { GeoJsResponse } from './types/geojs.js';
|
|
54
|
+
|
|
55
|
+
// #endregion ➤ 📦 Package Imports
|
|
56
|
+
|
|
57
|
+
// #region ➤ 📌 VARIABLES
|
|
58
|
+
|
|
59
|
+
// ╭────────────────────────────────────────────────────────────────────────╮
|
|
60
|
+
// │ NOTE: │
|
|
61
|
+
// │ Please add inside 'this' region the 'variables' that are to be │
|
|
62
|
+
// │ and are expected to be used by 'this' .svelte file / component. │
|
|
63
|
+
// │ IMPORTANT │
|
|
64
|
+
// │ Please, structure the imports as follows: │
|
|
65
|
+
// │ 1. export const / let [..] │
|
|
66
|
+
// │ 2. const [..] │
|
|
67
|
+
// │ 3. let [..] │
|
|
68
|
+
// │ 4. $: [..] │
|
|
69
|
+
// ╰────────────────────────────────────────────────────────────────────────╯
|
|
70
|
+
|
|
71
|
+
export let
|
|
72
|
+
/**
|
|
73
|
+
* @description
|
|
74
|
+
* 📝 Width device change onset number
|
|
75
|
+
*/
|
|
76
|
+
deviceWidthList: [number, number] = [768, 1024],
|
|
77
|
+
/**
|
|
78
|
+
* @description
|
|
79
|
+
* 📝 **author id**
|
|
80
|
+
*/
|
|
81
|
+
authorId: number | null = null,
|
|
82
|
+
/**
|
|
83
|
+
* @description
|
|
84
|
+
* 📝 `List` of **tag ids**
|
|
85
|
+
*/
|
|
86
|
+
authorArticleTagIds: number[] = []
|
|
87
|
+
;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @description
|
|
91
|
+
* 📝 Component Local Interface
|
|
92
|
+
*/
|
|
93
|
+
type IDeviceType =
|
|
94
|
+
| 'desktop'
|
|
95
|
+
| 'tablet'
|
|
96
|
+
| 'mobile'
|
|
97
|
+
;
|
|
98
|
+
|
|
99
|
+
let
|
|
100
|
+
/**
|
|
101
|
+
* @description
|
|
102
|
+
* 📝 Device type detected
|
|
103
|
+
*/
|
|
104
|
+
deviceType: IDeviceType = 'desktop',
|
|
105
|
+
/**
|
|
106
|
+
* @description
|
|
107
|
+
* 📝 Geo-Location response
|
|
108
|
+
*/
|
|
109
|
+
geoLocation: GeoJsResponse | null = null,
|
|
110
|
+
/**
|
|
111
|
+
* @description
|
|
112
|
+
* 📝 `Map` where, `key=ZoneId` and `value=HTMLElement`
|
|
113
|
+
*/
|
|
114
|
+
betarenaAdsInjectMap: Map < number, Element > = new Map()
|
|
115
|
+
;
|
|
116
|
+
|
|
117
|
+
// #endregion ➤ 📌 VARIABLES
|
|
118
|
+
|
|
119
|
+
// #region ➤ 🛠️ METHODS
|
|
120
|
+
|
|
121
|
+
// ╭────────────────────────────────────────────────────────────────────────╮
|
|
122
|
+
// │ NOTE: │
|
|
123
|
+
// │ Please add inside 'this' region the 'methods' that are to be │
|
|
124
|
+
// │ and are expected to be used by 'this' .svelte file / component. │
|
|
125
|
+
// │ IMPORTANT │
|
|
126
|
+
// │ Please, structure the imports as follows: │
|
|
127
|
+
// │ 1. function (..) │
|
|
128
|
+
// │ 2. async function (..) │
|
|
129
|
+
// ╰────────────────────────────────────────────────────────────────────────╯
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @author
|
|
133
|
+
* @migbash
|
|
134
|
+
* @summary
|
|
135
|
+
* 🟥 MAIN
|
|
136
|
+
* @description
|
|
137
|
+
* 📝 `Map` generation for `HTMLElements`.
|
|
138
|
+
* @returns { void }
|
|
139
|
+
*/
|
|
140
|
+
function generateElementMap
|
|
141
|
+
(
|
|
142
|
+
): void
|
|
143
|
+
{
|
|
144
|
+
const
|
|
145
|
+
/**
|
|
146
|
+
* @description
|
|
147
|
+
* 📝 `List` of `HTMLElements` identified on `page` for a target `zone`.
|
|
148
|
+
*/
|
|
149
|
+
htmlElementList = document.querySelectorAll('[data-betarena-zone-id]')
|
|
150
|
+
;
|
|
151
|
+
|
|
152
|
+
// ╭─────
|
|
153
|
+
// │ NOTE: |:| loop over elements detected as betarena elegible ads
|
|
154
|
+
// ╰─────
|
|
155
|
+
for (const elem of htmlElementList)
|
|
156
|
+
{
|
|
157
|
+
const
|
|
158
|
+
/**
|
|
159
|
+
* @description
|
|
160
|
+
* 📝 Value of Betarena Zone Id
|
|
161
|
+
*/
|
|
162
|
+
value = elem.attributes.getNamedItem('data-betarena-zone-id')?.value
|
|
163
|
+
;
|
|
164
|
+
|
|
165
|
+
if (!value) continue;
|
|
166
|
+
|
|
167
|
+
betarenaAdsInjectMap.set(parseInt(value), elem);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @author
|
|
175
|
+
* @migbash
|
|
176
|
+
* @summary
|
|
177
|
+
* 🟦 HELPER
|
|
178
|
+
* @description
|
|
179
|
+
* 📝 Generates adverts (+ complementary data) for appropiate zones
|
|
180
|
+
* @param { IAdsRequestBody } opts
|
|
181
|
+
* 💠 **[required]** `list` of zone ids
|
|
182
|
+
* @returns { Promise < void > }
|
|
183
|
+
*/
|
|
184
|
+
async function injectBetarenaAds
|
|
185
|
+
(
|
|
186
|
+
opts: IAdsRequestBody
|
|
187
|
+
): Promise < void >
|
|
188
|
+
{
|
|
189
|
+
if (!document) return;
|
|
190
|
+
|
|
191
|
+
const
|
|
192
|
+
/**
|
|
193
|
+
* @description
|
|
194
|
+
* 📝 Response from `fetch`
|
|
195
|
+
*/
|
|
196
|
+
response
|
|
197
|
+
= await postMod
|
|
198
|
+
<
|
|
199
|
+
IAdsRequestBody,
|
|
200
|
+
IAdsResponseBody
|
|
201
|
+
>
|
|
202
|
+
(
|
|
203
|
+
`${betarenaEndpoint}/ads`,
|
|
204
|
+
opts
|
|
205
|
+
)
|
|
206
|
+
;
|
|
207
|
+
|
|
208
|
+
// [🐞]
|
|
209
|
+
console.log(response);
|
|
210
|
+
|
|
211
|
+
const
|
|
212
|
+
/**
|
|
213
|
+
* @description
|
|
214
|
+
* 📝 `Map`
|
|
215
|
+
*/
|
|
216
|
+
dataMap = new Map((response as IAdsResponseBody).ads ?? []),
|
|
217
|
+
/**
|
|
218
|
+
* @description
|
|
219
|
+
* 📝 `Map`
|
|
220
|
+
*/
|
|
221
|
+
mapZoneIdToCampaignId = new Map ((response as IAdsResponseBody).mapZoneIdToCampaignId),
|
|
222
|
+
/**
|
|
223
|
+
* @description
|
|
224
|
+
* 📝 `Map`
|
|
225
|
+
*/
|
|
226
|
+
mapCreativeIdToCampaignId = new Map ((response as IAdsResponseBody).mapCampaignIdToCreativeId),
|
|
227
|
+
/**
|
|
228
|
+
* @description
|
|
229
|
+
* 📝 `Map`
|
|
230
|
+
*/
|
|
231
|
+
mapZoneToAuthorIds = new Map ((response as IAdsResponseBody).mapZoneIdToAuthorIds),
|
|
232
|
+
/**
|
|
233
|
+
* @description
|
|
234
|
+
* 📝 `Map`
|
|
235
|
+
*/
|
|
236
|
+
mapZoneToTagIds = new Map ((response as IAdsResponseBody).mapZoneIdToTagIds)
|
|
237
|
+
;
|
|
238
|
+
|
|
239
|
+
// ╭─────
|
|
240
|
+
// │ NOTE:
|
|
241
|
+
// │ ➤ [1] loop over retrieved/fetched elements, and
|
|
242
|
+
// │ ➤ [2] inject target `ads` in specific locations
|
|
243
|
+
// ╰─────
|
|
244
|
+
for (const [zoneId, element] of betarenaAdsInjectMap)
|
|
245
|
+
{
|
|
246
|
+
if (!geoLocation) continue;
|
|
247
|
+
|
|
248
|
+
const
|
|
249
|
+
/**
|
|
250
|
+
* @description
|
|
251
|
+
* 📝 **campaign ids** of respective **zone id**
|
|
252
|
+
*/
|
|
253
|
+
zoneIdCampaignIds
|
|
254
|
+
= (mapZoneIdToCampaignId.get(zoneId) ?? []),
|
|
255
|
+
/**
|
|
256
|
+
* @description
|
|
257
|
+
* 📝 `List` of creative data point(s)
|
|
258
|
+
*/
|
|
259
|
+
creativeAdData
|
|
260
|
+
= zoneIdCampaignIds.map
|
|
261
|
+
(
|
|
262
|
+
x =>
|
|
263
|
+
{
|
|
264
|
+
if (dataMap.has(x))
|
|
265
|
+
return dataMap.get(x);
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
;
|
|
269
|
+
|
|
270
|
+
for (const adData of creativeAdData ?? [])
|
|
271
|
+
{
|
|
272
|
+
new WidgetAdGeneral
|
|
273
|
+
(
|
|
274
|
+
{
|
|
275
|
+
target: element,
|
|
276
|
+
props:
|
|
277
|
+
{
|
|
278
|
+
adData
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ╭─────
|
|
286
|
+
// │ NOTE:
|
|
287
|
+
// │ ➤ inject of 'global' placements ads (based on global page data)
|
|
288
|
+
// ╰─────
|
|
289
|
+
if (authorId || authorArticleTagIds.length > 0)
|
|
290
|
+
{
|
|
291
|
+
for (const [zoneId, authorIds] of mapZoneToAuthorIds)
|
|
292
|
+
{
|
|
293
|
+
if (authorId && !authorIds.includes(authorId))
|
|
294
|
+
continue;
|
|
295
|
+
;
|
|
296
|
+
|
|
297
|
+
const
|
|
298
|
+
/**
|
|
299
|
+
* @description
|
|
300
|
+
* 📝 **campaign ids** of respective **zone id**
|
|
301
|
+
*/
|
|
302
|
+
zoneIdCampaignIds
|
|
303
|
+
= (mapZoneIdToCampaignId.get(zoneId) ?? []),
|
|
304
|
+
/**
|
|
305
|
+
* @description
|
|
306
|
+
* 📝 `List` of creative data point(s)
|
|
307
|
+
*/
|
|
308
|
+
creativeAdData
|
|
309
|
+
= zoneIdCampaignIds.map
|
|
310
|
+
(
|
|
311
|
+
x =>
|
|
312
|
+
{
|
|
313
|
+
if (dataMap.has(x))
|
|
314
|
+
return dataMap.get(x);
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
;
|
|
318
|
+
|
|
319
|
+
for (const adData of creativeAdData)
|
|
320
|
+
{
|
|
321
|
+
new WidgetAdvertSlide
|
|
322
|
+
(
|
|
323
|
+
{
|
|
324
|
+
target: document.body,
|
|
325
|
+
props:
|
|
326
|
+
{
|
|
327
|
+
adData
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for (const [zoneId, tagIds] of mapZoneToTagIds)
|
|
335
|
+
{
|
|
336
|
+
if (authorId && !tagIds.includes(authorId))
|
|
337
|
+
continue;
|
|
338
|
+
;
|
|
339
|
+
|
|
340
|
+
const
|
|
341
|
+
/**
|
|
342
|
+
* @description
|
|
343
|
+
* 📝 **campaign ids** of respective **zone id**
|
|
344
|
+
*/
|
|
345
|
+
zoneIdCampaignIds
|
|
346
|
+
= (mapZoneIdToCampaignId.get(zoneId) ?? []),
|
|
347
|
+
/**
|
|
348
|
+
* @description
|
|
349
|
+
* 📝 `List` of creative data point(s)
|
|
350
|
+
*/
|
|
351
|
+
creativeAdData
|
|
352
|
+
= zoneIdCampaignIds.map
|
|
353
|
+
(
|
|
354
|
+
x =>
|
|
355
|
+
{
|
|
356
|
+
if (dataMap.has(x))
|
|
357
|
+
return dataMap.get(x);
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
;
|
|
361
|
+
|
|
362
|
+
for (const adData of creativeAdData)
|
|
363
|
+
{
|
|
364
|
+
new WidgetAdvertSlide
|
|
365
|
+
(
|
|
366
|
+
{
|
|
367
|
+
target: document.body,
|
|
368
|
+
props:
|
|
369
|
+
{
|
|
370
|
+
adData
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* @author
|
|
383
|
+
* @migbash
|
|
384
|
+
* @summary
|
|
385
|
+
* 🟥 MAIN
|
|
386
|
+
* @description
|
|
387
|
+
* 📝 Logic bundle for advert initialization
|
|
388
|
+
* @returns { Promise < void > }
|
|
389
|
+
*/
|
|
390
|
+
async function initialize
|
|
391
|
+
(
|
|
392
|
+
): Promise < void >
|
|
393
|
+
{
|
|
394
|
+
detectDeviceType();
|
|
395
|
+
geoLocation = await getUserLocation();
|
|
396
|
+
generateElementMap();
|
|
397
|
+
|
|
398
|
+
// [🐞]
|
|
399
|
+
// console.log('elements', betarenaAdsInjectMap);
|
|
400
|
+
// console.log('elements', zoneIds);
|
|
401
|
+
|
|
402
|
+
injectBetarenaAds
|
|
403
|
+
(
|
|
404
|
+
{
|
|
405
|
+
deviceType,
|
|
406
|
+
isoCountryCode: geoLocation?.country_code ?? 'EB',
|
|
407
|
+
authorId,
|
|
408
|
+
tagIds: authorArticleTagIds,
|
|
409
|
+
zoneIds: [...betarenaAdsInjectMap.keys()]
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// #endregion ➤ 🛠️ METHODS
|
|
417
|
+
|
|
418
|
+
// #region ➤ 🔄 LIFECYCLE [SVELTE]
|
|
419
|
+
|
|
420
|
+
// ╭────────────────────────────────────────────────────────────────────────╮
|
|
421
|
+
// │ NOTE: │
|
|
422
|
+
// │ Please add inside 'this' region the 'logic' that should run │
|
|
423
|
+
// │ immediately and as part of the 'lifecycle' of svelteJs, │
|
|
424
|
+
// │ as soon as 'this' .svelte file is ran. │
|
|
425
|
+
// ╰────────────────────────────────────────────────────────────────────────╯
|
|
426
|
+
|
|
427
|
+
onMount
|
|
428
|
+
(
|
|
429
|
+
async () =>
|
|
430
|
+
{
|
|
431
|
+
betarenaAdEngineStore.useLocalStorage();
|
|
432
|
+
await initialize();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// #endregion ➤ 🔄 LIFECYCLE [SVELTE]
|
|
438
|
+
|
|
439
|
+
</script>
|
|
440
|
+
|
|
441
|
+
<svelte:window
|
|
442
|
+
on:resize=
|
|
443
|
+
{
|
|
444
|
+
() =>
|
|
445
|
+
{
|
|
446
|
+
detectDeviceType();
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/>
|
|
451
|
+
|
|
452
|
+
<!--
|
|
453
|
+
╭──────────────────────────────────────────────────────────────────────────────────╮
|
|
454
|
+
│ 💠 Svelte Component HTML │
|
|
455
|
+
┣──────────────────────────────────────────────────────────────────────────────────┫
|
|
456
|
+
│ ➤ HINT: │ Use 'Ctrl + Space' to autocomplete global class=styles, dynamically │
|
|
457
|
+
│ │ imported from './static/app.css' │
|
|
458
|
+
│ ➤ HINT: │ access custom Betarena Scores VScode Snippets by typing emmet-like │
|
|
459
|
+
│ │ abbrev. │
|
|
460
|
+
╰──────────────────────────────────────────────────────────────────────────────────╯
|
|
461
|
+
-->
|
|
462
|
+
|
|
463
|
+
<!--
|
|
464
|
+
╭─────
|
|
465
|
+
│ ➤ Testing [🐞]
|
|
466
|
+
╰─────
|
|
467
|
+
-->
|
|
468
|
+
<div>
|
|
469
|
+
|
|
470
|
+
<p>
|
|
471
|
+
Device Type: {deviceType}
|
|
472
|
+
</p>
|
|
473
|
+
|
|
474
|
+
<p>
|
|
475
|
+
{geoLocation?.country_code}
|
|
476
|
+
</p>
|
|
477
|
+
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<div
|
|
481
|
+
data-betarena-zone-id=1
|
|
482
|
+
>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<!--
|
|
486
|
+
<hr>
|
|
487
|
+
|
|
488
|
+
<div
|
|
489
|
+
data-betarena-zone-id=2
|
|
490
|
+
>
|
|
491
|
+
</div>
|
|
492
|
+
-->
|