PageSourceSearch

https://www.elveszettkutyak.hu/w/459.js

js elveszettkutyak.hu collected 2026-10-07 10:03:48 UTC 34,063 bytes, 569 lines download raw bytes

1(function(){
2  var SCRIPT = document.currentScript;
3  var SID = (SCRIPT && SCRIPT.dataset && SCRIPT.dataset.siteId) ? +SCRIPT.dataset.siteId : (window.__SID || 0);
4  if (!SID) {
5    // Try extract from src URL: /w/123.js
6    var m = SCRIPT && SCRIPT.src && SCRIPT.src.match(/\/w\/(\d+)\.js/);
7    if (m) SID = +m[1];
8  }
9  if (!SID) return;
10  var BASE = '';
11  var LDR;
12
13  function showLoader() {
14    if (LDR) { LDR.style.display = 'flex'; return; }
15    LDR = document.createElement('div');
16    LDR.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:1000000;display:flex;align-items:center;justify-content:center;';
17    var s = document.createElement('div');
18    s.style.cssText = 'width:48px;height:48px;border:4px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:_wSpin .6s linear infinite;';
19    var st = document.createElement('style');
20    st.textContent = '@keyframes _wSpin{to{transform:rotate(360deg)}}';
21    document.head.appendChild(st);
22    LDR.appendChild(s);
23    document.body.appendChild(LDR);
24    window.addEventListener('pageshow', function(){ if (LDR) LDR.style.display='none'; });
25  }
26  function hideLoader(){ if (LDR) LDR.style.display='none'; }
27
28  function go(src) {
29    showLoader();
30    fetch(BASE + '/go/url?s=' + SID + '&src=' + encodeURIComponent(src))
31      .then(function(r){ return r.json(); })
32      .then(function(d){ if (d && d.u) location.href = d.u; else hideLoader(); })
33      .catch(hideLoader);
34  }
35
36  function esc(s){ return String(s||'').replace(/[&<>"']/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]; }); }
37
38  // Header Banner popup CSS — colors come from CSS vars set on :host based on the site's
39  // mainColor / accentColor / accentTextColor — popup picks up each site's brand palette.
40  var POPUP_CSS = ''
41    + ':host{all:initial;}'
42    + '*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.3;}'
43    /* Overlay */
44    + '.ovr{position:fixed;inset:0;background:rgba(0,0,0,.85);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);display:flex;align-items:flex-start;justify-content:center;z-index:999999;opacity:0;transition:opacity .4s;padding:20px;overflow-y:auto;-webkit-overflow-scrolling:touch;}'
45    + '.ovr.show{opacity:1;}'
46    + '.wrap{position:relative;max-width:420px;width:100%;flex-shrink:0;margin:auto;animation:popIn .5s cubic-bezier(.34,1.56,.64,1);}'
47    + '@keyframes popIn{from{transform:scale(.8) translateY(50px);opacity:0}to{transform:scale(1) translateY(0);opacity:1}}'
48    + '.banner{position:relative;border-radius:14px;overflow:hidden;width:100%;animation:bannerIn .4s cubic-bezier(.16,1,.3,1) forwards;background:var(--bg);}'
49    + '@keyframes bannerIn{from{opacity:0;transform:scale(.92) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}'
50    /* BG image with dark gradient overlay */
51    + '.bg{position:absolute;inset:0;background-size:cover;background-position:center;z-index:0;}'
52    + '.bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,color-mix(in srgb,var(--bg) 70%, transparent) 0%,color-mix(in srgb,var(--bg) 88%, transparent) 40%,var(--bg) 100%);}'
53    /* Close — outside top-right, themed */
54    + '.close{position:absolute;top:-7px;right:-7px;width:28px;height:28px;background:color-mix(in srgb,var(--bg) 80%, #333);border:2px solid color-mix(in srgb,var(--accent) 50%, rgba(255,255,255,.3));color:#fff;border-radius:50%;font-size:18px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:10;transition:all .2s;padding:0;}'
55    + '.close:hover{transform:scale(1.1);text-shadow:0 0 12px var(--accent);border-color:var(--accent);}'
56    /* Warning strip — keep red so it stays alarming */
57    + '.warn{position:relative;z-index:1;background:linear-gradient(135deg,rgba(180,40,40,.35),rgba(120,30,30,.15));border-bottom:1px solid rgba(255,80,80,.15);padding:14px 20px;text-align:center;}'
58    + '.warn-icon{display:inline-block;width:18px;height:18px;background:#d44;border-radius:50%;color:#fff;font-size:12px;font-weight:900;line-height:18px;text-align:center;margin-right:6px;vertical-align:middle;flex-shrink:0;animation:warnPulse 1.5s ease-in-out infinite;}'
59    + '@keyframes warnPulse{0%,100%{box-shadow:0 0 0 0 rgba(204,51,51,.6)}50%{box-shadow:0 0 0 6px rgba(204,51,51,0)}}'
60    + '.warn-text{color:#ff8a8a;font-size:14px;font-weight:600;line-height:1.5;display:inline;}'
61    + '.warn-text b{color:#fff;font-weight:800;}'
62    /* Body */
63    + '.body{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;padding:22px 24px 24px;text-align:center;}'
64    + '.recommend{color:rgba(255,255,255,.55);font-size:11px;text-transform:uppercase;letter-spacing:2px;margin-bottom:18px;font-weight:600;}'
65    /* Logo with accent glow + pulse */
66    + '.logo-wrap{position:relative;margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid rgba(255,255,255,.08);width:100%;display:flex;justify-content:center;}'
67    + '.logo-wrap::before{content:"";position:absolute;inset:-20px;background:radial-gradient(ellipse,color-mix(in srgb,var(--accent) 18%, transparent) 0%,transparent 70%);z-index:0;animation:logoGlow 3s ease-in-out infinite;}'
68    + '.logo{position:relative;z-index:1;max-height:55px;max-width:190px;object-fit:contain;filter:drop-shadow(0 2px 10px rgba(0,0,0,.4));animation:logoPulse 3s ease-in-out infinite;}'
69    + '@keyframes logoGlow{0%,100%{opacity:.6;transform:scale(1)}50%{opacity:1;transform:scale(1.1)}}'
70    + '@keyframes logoPulse{0%,100%{transform:scale(1);filter:drop-shadow(0 2px 10px rgba(0,0,0,.4))}50%{transform:scale(1.03);filter:drop-shadow(0 0 18px color-mix(in srgb,var(--accent) 35%, transparent))}}'
71    /* Bonus card */
72    + '.bonus{background:linear-gradient(135deg,rgba(255,255,255,.07),rgba(255,255,255,.02));border:1px solid color-mix(in srgb,var(--accent) 25%, rgba(255,255,255,.08));border-radius:10px;padding:16px 20px;margin-bottom:14px;width:100%;}'
73    + '.bonus-label{font-size:11px;color:rgba(255,255,255,.55);text-transform:uppercase;letter-spacing:2.5px;margin-bottom:8px;font-weight:600;}'
74    + '.prize{font-size:20px;font-weight:800;color:#fff;line-height:1.3;}'
75    + '.prize .pct{color:var(--accent);text-shadow:0 0 18px color-mix(in srgb,var(--accent) 50%, transparent);}'
76    + '.sub{font-size:11px;color:color-mix(in srgb,var(--accent) 70%, transparent);margin-top:10px;text-transform:uppercase;letter-spacing:1.5px;font-weight:700;}'
77    + '.exclusive{margin-top:4px;margin-bottom:14px;font-size:11px;color:rgba(255,255,255,.55);letter-spacing:.5px;}'
78    /* CTA — site accent palette (matches hero-cta on the site exactly) */
79    + '.cta{display:block;width:100%;padding:14px 20px;border-radius:8px;border:none;cursor:pointer;font-size:15px;font-weight:800;color:var(--accent-text);text-decoration:none;text-transform:uppercase;letter-spacing:1px;text-align:center;position:relative;overflow:hidden;background:var(--accent);box-shadow:0 4px 18px color-mix(in srgb,var(--accent) 30%, transparent);transition:transform .15s,box-shadow .15s,filter .15s;animation:btnPulse 2s ease-in-out infinite;}'
80    + '.cta::after{content:"";position:absolute;top:0;left:-100%;width:60%;height:100%;background:linear-gradient(105deg,transparent 30%,rgba(255,255,255,.28) 50%,transparent 70%);animation:btnShine 3s ease-in-out infinite;}'
81    + '.cta:hover{transform:translateY(-1px);filter:brightness(1.1);box-shadow:0 8px 26px color-mix(in srgb,var(--accent) 45%, transparent);}'
82    + '@keyframes btnPulse{0%,100%{box-shadow:0 4px 18px color-mix(in srgb,var(--accent) 30%, transparent)}50%{box-shadow:0 4px 26px color-mix(in srgb,var(--accent) 50%, transparent)}}'
83    + '@keyframes btnShine{0%,70%{left:-100%}100%{left:150%}}'
84    /* Timer */
85    + '.tim{margin-top:14px;width:100%;}'
86    + '.tim-text{font-size:10px;color:rgba(255,255,255,.4);letter-spacing:.5px;margin-bottom:5px;text-transform:lowercase;}'
87    + '.tim-text span{color:rgba(255,255,255,.7);font-variant-numeric:tabular-nums;font-weight:700;}'
88    + '.tim-bar{width:100%;height:3px;background:rgba(255,255,255,.08);border-radius:2px;overflow:hidden;}'
89    + '.tim-fill{height:100%;background:var(--accent);border-radius:2px;width:100%;transition:width 1s linear;}';
90
91  // CSS for the sticky footer — injected into Shadow DOM (NOT global page) so the
92  // site's own resets/typography can't bleed in.
93  // Colors come from CSS vars set on :host based on the site's mainColor / accentColor /
94  // accentTextColor — sticky banner picks up each site's brand palette automatically.
95  var STICKY_CSS = ''
96    + ':host{all:initial;}'
97    + '*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.3;}'
98    + '.sf{position:fixed;left:0;right:0;bottom:0;z-index:999998;'
99    +     'background:'
100    +       'linear-gradient(180deg,rgba(255,255,255,0.07),rgba(255,255,255,0.02)),'
101    +       'var(--bg);'  /* solid site bg — keeps iOS Safari toolbar tint matching the site */
102    +     'border-top:3px solid var(--accent);'
103    +     'box-shadow:0 -8px 32px rgba(0,0,0,0.6),inset 0 1px 0 rgba(255,255,255,0.08);'
104    +     'transform:translateY(100%);transition:transform .4s ease-out;color:var(--text);}'
105    + '.sf.show{transform:translateY(0);}'
106    + '.inner{padding:14px 56px 14px 20px;position:relative;max-width:1100px;margin:0 auto;display:flex;align-items:center;gap:16px;flex-wrap:wrap;}'
107    + '.x{position:absolute;top:8px;right:10px;width:26px;height:26px;border:none;background:rgba(255,255,255,0.1);color:rgba(255,255,255,0.6);border-radius:50%;cursor:pointer;font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center;}'
108    + '.x:hover{background:rgba(255,255,255,0.2);color:#fff;}'
109    + '.lg{display:flex;align-items:center;gap:10px;flex-shrink:0;}'
110    + '.lc{height:56px;min-width:56px;max-width:160px;display:flex;align-items:center;justify-content:center;position:relative;flex-shrink:0;}'
111    + '.lc img{height:100%;width:auto;max-width:100%;object-fit:contain;display:block;}'
112    + '.lc.old{opacity:.55;filter:grayscale(.5);}'
113    + '.arr{color:var(--accent);font-size:28px;font-weight:700;line-height:1;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;flex-shrink:0;animation:arrSlide 1.4s ease-in-out infinite;}'
114    + '@keyframes arrSlide{0%,100%{transform:translateX(0);opacity:.7}50%{transform:translateX(6px);opacity:1}}'
115    + '.tx{flex:1;
115min-width:200px;font-size:13px;color:var(--text);line-height:1.4;}'
116    + '.tx b,.tx strong{color:var(--accent);font-weight:700;}'
117    + '.cta{padding:11px 22px;border:none;border-radius:8px;background:var(--accent);color:var(--accent-text);font-size:14px;font-weight:800;letter-spacing:.5px;text-transform:uppercase;cursor:pointer;flex-shrink:0;transition:transform .15s,box-shadow .15s;white-space:nowrap;display:inline-flex;align-items:center;justify-content:center;gap:8px;}'
118    + '.cta:hover{transform:translateY(-1px);box-shadow:0 6px 18px color-mix(in srgb,var(--accent) 50%, transparent);}'
119    + '.cta .ctarr{display:inline-block;font-size:18px;line-height:1;animation:ctaSlide 1.4s ease-in-out infinite;}'
120    + '@keyframes ctaSlide{0%,100%{transform:translateX(0)}50%{transform:translateX(4px)}}'
121    + '@media(max-width:720px){'
122    +   '.inner{padding:10px 12px 12px 12px;gap:10px;}'
123    +   '.x{top:6px;right:8px;width:22px;height:22px;font-size:16px;}'
124    +   '.lg{width:100%;flex-basis:100%;justify-content:center;order:1;padding:0 32px;}'  /* keep close button visible */
125    +   '.lc{height:42px;min-width:42px;max-width:120px;}'
126    +   '.tx{font-size:12px;width:100%;flex-basis:100%;order:3;min-width:0;text-align:center;}'
127    +   '.cta{padding:15px 14px;font-size:14px;width:100%;order:4;}'
128    + '}';
129
130  function renderSticky(cfg) {
131    if (!cfg.sticky || !cfg.sticky.enabled) return;
132
133    // Shadow DOM host — totally isolated from the site's CSS.
134    // CSS vars set on :host carry the site's palette into the shadow tree.
135    var host = document.createElement('div');
136    var bg = cfg.mainColor || '#0d0d1a';
137    var ac = cfg.accentColor || '#ffd700';
138    var act = cfg.accentTextColor || '#09090b';
139    host.style.cssText = 'all:initial;--bg:' + bg + ';--accent:' + ac + ';--accent-text:' + act + ';--text:#cbd5e1;';
140    var root = host.attachShadow ? host.attachShadow({mode: 'closed'}) : host;
141    var styleEl = document.createElement('style');
142    styleEl.textContent = STICKY_CSS;
143    root.appendChild(styleEl);
144
145    var foot = document.createElement('div'); foot.className = 'sf';
146    var inner = document.createElement('div'); inner.className = 'inner';
147
148    var x = document.createElement('button'); x.className = 'x'; x.innerHTML = '&times;';
149    var RESHOW = 10000;
150    x.addEventListener('click', function(){
151      foot.classList.remove('show');
152      // Drop padding-bottom and reset --sticky-bar-height so scroll-top etc.
153      // settle back to their default position while the sticky is hidden.
154      syncBarHeight();
155      // Re-show after the cool-off period — but only if the menu isn't
156      // open at that moment. syncMenuState handles both checks.
157      setTimeout(function(){ syncMenuState(); }, RESHOW);
158    });
159    inner.appendChild(x);
160
161    // Logos: site (old, ✗) → arrow → brand (new, ✓, clickable)
162    if (cfg.siteLogo || cfg.brandLogo) {
163      var lg = document.createElement('div'); lg.className = 'lg';
164      if (cfg.siteLogo) {
165        var oldC = document.createElement('div'); oldC.className = 'lc old';
166        var oi = document.createElement('img'); oi.src = cfg.siteLogo; oi.alt = '';
167        var ar = document.createElement('div'); ar.className = 'arr'; ar.textContent = '→';
168        // If site logo 404s (assets folder name changed since deploy), hide both the
169        // logo card AND the arrow — leave just the brand logo without comparison.
170        oi.onerror = function(){ oldC.style.display='none'; ar.style.display='none'; };
171        oldC.appendChild(oi); lg.appendChild(oldC); lg.appendChild(ar);
172      }
173      if (cfg.brandLogo) {
174        var newC = document.createElement('div'); newC.className = 'lc new'; newC.style.cursor = 'pointer';
175        var ni = document.createElement('img'); ni.src = cfg.brandLogo; ni.alt = cfg.brandName || ''; ni.onerror = function(){ this.style.display='none'; };
176        newC.appendChild(ni);
177        newC.addEventListener('click', function(){ go(window.__WCFG && window.__WCFG.ctaHashes && window.__WCFG.ctaHashes.sticky_logo || 'sticky_logo'); });
178        lg.appendChild(newC);
179      }
180      inner.appendChild(lg);
181    }
182
183    // Custom text — site override → translation pack → minimal fallback
184    var tr = cfg.translations || {};
185    var rawText = (cfg.sticky && cfg.sticky.text)
186      || tr.stickyText
187      || ((cfg.prize || cfg.prizeDetail)
188          ? ((cfg.countryFlag ? cfg.countryFlag + ' ' : '') + 'Get ' + (cfg.prize || '') + ' ' + (cfg.prizeDetail || '') + ' at <b>{new_brand}</b>')
189          : 'Play at <b>{new_brand}</b>');
190    rawText = rawText
191      .replace(/\{prize\}/g, esc(cfg.prize || ''))
192      .replace(/\{prize_detail\}/g, esc(cfg.prizeDetail || ''))
193      .replace(/\{prize_sub\}/g, esc(cfg.prizeSub || ''))
194      .replace(/\{new_brand\}/g, '<b>' + esc(cfg.brandName || '') + '</b>')
195      .replace(/\{old_brand\}/g, '<b>' + esc(cfg.siteName || '') + '</b>')
196      .replace(/\{site_name\}/g, '<b>' + esc(cfg.siteName || '') + '</b>')
197      .replace(/\{brand\}/g, esc(cfg.brandName || ''))
198      .replace(/\{flag\}/g, cfg.countryFlag || '')
199      .replace(/\{country_adj\}/g, esc(cfg.countryAdj || cfg.countryName || ''))
200      .replace(/\{country\}/g, esc(cfg.countryName || ''));
201    var tx = document.createElement('div'); tx.className = 'tx'; tx.innerHTML = rawText;
202    inner.appendChild(tx);
203
204    // CTA button — site override → translation pack → "Play Now"
205    var ctaTxt = (cfg.sticky && cfg.sticky.button_text) || (cfg.sticky && cfg.sticky.buttonText) || tr.buttonText || 'Play Now';
206    var cta = document.createElement('button'); cta.className = 'cta';
207    cta.innerHTML = esc(ctaTxt) + '<span class="ctarr">→</span>';
208    cta.addEventListener('click', function(){ go(window.__WCFG && window.__WCFG.ctaHashes && window.__WCFG.ctaHashes.sticky_btn || 'sticky_btn'); });
209    inner.appendChild(cta);
210
211    foot.appendChild(inner);
212    root.appendChild(foot);
213    document.body.appendChild(host);
214    // Show the sticky bar after slide-in delay — UNLESS the user has
215    // already opened the mobile menu in those 1.5s. syncMenuState reads
216    // body.menu-open and only flips `.show` when allowed.
217    setTimeout(function(){ syncMenuState(); }, 1500);
218
219    // Reserve space at the bottom of the document equal to the sticky's
220    // height so the page footer ends ABOVE the sticky band — sticky 
220stays
221    // `position:fixed bottom:0` always, but no longer overlays copyright/RG.
222    // Note: `host` is a wrapper, the visible bar `.sf` is position:fixed so
223    // its size doesn't bubble up to host.offsetHeight — we measure .sf via
224    // the shadow root directly. Use !important to win against any theme
225    // that pins body padding.
226    function syncBarHeight(){
227      // Only reserve space + expose the CSS variable while the sticky is
228      // actually visible. When closed (X click) we drop both so other
229      // fixed-position UI (scroll-top, chat bubbles) settles back down.
230      var visible = foot.classList.contains('show');
231      var h = visible ? foot.getBoundingClientRect().height : 0;
232      if (h) {
233        document.body.style.setProperty('padding-bottom', h + 'px', 'important');
234        document.documentElement.style.setProperty('--sticky-bar-height', h + 'px');
235      } else {
236        document.body.style.removeProperty('padding-bottom');
237        document.documentElement.style.setProperty('--sticky-bar-height', '0px');
238      }
239    }
240    setTimeout(syncBarHeight, 1700);    // after slide-in animation finishes
241    setTimeout(syncBarHeight, 3000);    // recheck once layout settles
242    window.addEventListener('resize', syncBarHeight);
243    if ('ResizeObserver' in window) {
244      new ResizeObserver(syncBarHeight).observe(foot);
245    }
246
247    // Hide the sticky bar while the mobile menu is open. The theme's
248    // main.js sets `body.menu-open` on burger click — we observe that
249    // class and toggle the bar accordingly.
250    function syncMenuState(){
251      if (document.body.classList.contains('menu-open')) {
252        foot.classList.remove('show');
253      } else {
254        foot.classList.add('show');
255      }
256      syncBarHeight();
257    }
258    new MutationObserver(syncMenuState).observe(document.body, {
259      attributes: true, attributeFilter: ['class']
260    });
261  }
262
263  // Header banner popup (header CTA click → modal). Mirrors old weblovers.vip widget pixel-for-pixel.
264  function showHeaderBanner(cfg, src) {
265    var tr = cfg.translations || {};
266    var pop = cfg.popup || {};
267
268    function fill(tpl){
269      return String(tpl||'')
270        .replace(/\{old_brand\}/g, esc(cfg.siteName || ''))
271        .replace(/\{site_name\}/g, esc(cfg.siteName || ''))
272        .replace(/\{new_brand\}/g, esc(cfg.brandName || ''))
273        .replace(/\{brand\}/g, esc(cfg.brandName || ''))
274        .replace(/\{flag\}/g, cfg.countryFlag || '')
275        .replace(/\{country_adj\}/g, esc(cfg.countryAdj || cfg.countryName || ''))
276        .replace(/\{country\}/g, esc(cfg.countryName || ''));
277    }
278
279    // Shadow DOM host — site palette propagates via CSS vars on :host
280    var host = document.createElement('div');
281    var bg = cfg.mainColor || '#0d0d1a';
282    var ac = cfg.accentColor || '#ffd700';
283    var act = cfg.accentTextColor || '#09090b';
284    host.style.cssText = 'all:initial;--bg:' + bg + ';--accent:' + ac + ';--accent-text:' + act + ';';
285    var root = host.attachShadow ? host.attachShadow({mode:'closed'}) : host;
286    var styleEl = document.createElement('style');
287    styleEl.textContent = POPUP_CSS;
288    root.appendChild(styleEl);
289
290    var ovr = document.createElement('div'); ovr.className = 'ovr';
291    var wrap = document.createElement('div'); wrap.className = 'wrap';
292    var banner = document.createElement('div'); banner.className = 'banner';
293
294    // Background image / fallback gradient
295    var bg = document.createElement('div'); bg.className = 'bg';
296    if (cfg.brandBg) {
297      bg.style.backgroundImage = 'url(' + cfg.brandBg + ')';
298    } else if (cfg.brandBgColor) {
299      bg.style.background = 'linear-gradient(180deg,' + cfg.brandBgColor + ' 0%,' + cfg.brandBgColor + ' 30%,#0a0a1a 100%)';
300    } else {
301      bg.style.background = 'linear-gradient(180deg,#0d0d1a 0%,#1a1a3e 40%,#0f1f3d 100%)';
302    }
303    banner.appendChild(bg);
304
305    // Close (cleanup also clears timer). Mount on .wrap (not .banner) so it sits OUTSIDE
306    // the banner's overflow:hidden boundary — otherwise the rounded corner clips it.
307    var x = document.createElement('button'); x.className = 'close'; x.innerHTML = '&times;';
308    x.addEventListener('click', function(){ try{ clearInterval(timer); }catch(e){} host.remove(); });
309
310    // Warning strip — "{old_brand} is now closed in {flag} {country}"
311    var warnTpl = (pop.warningText) || tr.bannerRestricted || '{old_brand} is now closed in {flag} {country}';
312    var warn = document.createElement('div'); warn.className = 'warn';
313    var warnTxt = document.createElement('span'); warnTxt.className = 'warn-text';
314    var ic = '<span class="warn-icon">!</span>';
315    // siteName gets bolded inside the warning
316    var rendered = fill(warnTpl);
317    var siteEsc = esc(cfg.siteName || '');
318    if (siteEsc && rendered.indexOf(siteEsc) >= 0) {
319      rendered = rendered.split(siteEsc).join('<b>' + siteEsc + '</b>');
320    }
321    warnTxt.innerHTML = rendered;
322    warn.innerHTML = ic;
323    warn.appendChild(warnTxt);
324    banner.appendChild(warn);
325
326    var body = document.createElement('div'); body.className = 'body';
327
328    // Recommend text (small uppercase yellow, above logo)
329    var recTpl = (pop.recommendText) || tr.bannerRecommend || 'Play through our partner:';
330    var rec = document.createElement('div');
330 rec.className = 'recommend'; rec.textContent = fill(recTpl);
331    body.appendChild(rec);
332
333    // Logo with glow + pulse
334    if (cfg.brandLogo) {
335      var lw = document.createElement('div'); lw.className = 'logo-wrap';
336      var img = document.createElement('img'); img.className = 'logo';
337      img.src = cfg.brandLogo; img.alt = cfg.brandName || '';
338      lw.appendChild(img);
339      body.appendChild(lw);
340    }
341
342    // Bonus card
343    var bonus = document.createElement('div'); bonus.className = 'bonus';
344    var bonusLabel = tr.bannerBonusLabel || 'WELCOME BONUS';
345    var bl = document.createElement('div'); bl.className = 'bonus-label'; bl.textContent = bonusLabel;
346    bonus.appendChild(bl);
347    var prizeVal = cfg.prize || '100%';
348    var prizeDetail = cfg.prizeDetail || '';
349    var pr = document.createElement('div'); pr.className = 'prize';
350    pr.innerHTML = '<span class="pct">' + esc(prizeVal) + '</span>' + (prizeDetail ? ' ' + esc(prizeDetail) : '');
351    bonus.appendChild(pr);
352    if (cfg.prizeSub) {
353      var sub = document.createElement('div'); sub.className = 'sub'; sub.textContent = cfg.prizeSub;
354      bonus.appendChild(sub);
355    }
356    body.appendChild(bonus);
357
358    // Exclusive line — "🇨🇦 Exclusive for Canadian players only"
359    var exclTpl = (pop.exclusiveText) || tr.bannerExclusive;
360    if (exclTpl) {
361      var excl = document.createElement('div'); excl.className = 'exclusive'; excl.innerHTML = fill(exclTpl);
362      body.appendChild(excl);
363    }
364
365    // CTA (animated green)
366    var claimTpl = (pop.claimText) || tr.claim || 'CLAIM YOUR BONUS';
367    var cta = document.createElement('button'); cta.className = 'cta'; cta.textContent = fill(claimTpl);
368    cta.addEventListener('click', function(){ try{ clearInterval(timer); }catch(e){} go(src + '_popup'); });
369    body.appendChild(cta);
370
371    // Timer + bar
372    var seconds = (pop.countdown) || 15;
373    var totalSec = seconds;
374    var timWrap = document.createElement('div'); timWrap.className = 'tim';
375    var timTxt = document.createElement('div'); timTxt.className = 'tim-text';
376    var timBar = document.createElement('div'); timBar.className = 'tim-bar';
377    var timFill = document.createElement('div'); timFill.className = 'tim-fill';
378    timBar.appendChild(timFill);
379    timWrap.appendChild(timTxt); timWrap.appendChild(timBar);
380    body.appendChild(timWrap);
381
382    var redirectTpl = tr.bannerRedirect || 'redirecting to {brand} in {time}...';
383    function paint(){
384      var txt = redirectTpl
385        .replace(/\{brand\}/g, esc(cfg.brandName || 'partner'))
386        .replace(/\{time\}/g, '<span>' + seconds + 's</span>');
387      timTxt.innerHTML = txt;
388      timFill.style.width = (seconds / totalSec * 100) + '%';
389    }
390    paint();
391    var timer = setInterval(function(){
392      seconds--; paint();
393      if (seconds <= 0) { clearInterval(timer); go(src + '_auto'); }
394    }, 1000);
395
396    banner.appendChild(body);
397    wrap.appendChild(banner);
398    wrap.appendChild(x);  /* mount AFTER banner so close button is on top */
399    ovr.appendChild(wrap);
400    root.appendChild(ovr);
401    document.body.appendChild(host);
402
403    // Trigger fade-in
404    requestAnimationFrame(function(){ ovr.classList.add('show'); });
405  }
406
407  // Floating "Play Online" CTA — a persistent affiliate button that fades in
408  // once the visitor has scrolled through the slots (slots are CTAs too). Built
409  // in a closed Shadow DOM so it's fully isolated from the site's (randomized)
410  // CSS and never collides with page classes.
411  function renderFloating(cfg) {
412    if (!cfg.floatingCta || !cfg.floatingCta.enabled) return;
413    // Colour must match the site's own CTA buttons exactly. Read it straight
414    // from the page's CSS variables (--accent-color / --accent-text-color) —
415    // the very same vars the theme buttons use — so the floating CTA can never
416    // drift from them (e.g. if the DB colour and the deployed build differ).
417    // These vars aren't randomized (only classes are), so the names are stable.
418    // Fall back to the config-provided colours, then a hard default.
419    var rootCS = getComputedStyle(document.documentElement);
420    var ac = (rootCS.getPropertyValue('--accent-color') || '').trim() || cfg.accentColor || '#ffd700';
421    var act = (rootCS.getPropertyValue('--accent-text-color') || '').trim() || cfg.accentTextColor || '#09090b';
422    var label = cfg.floatingCta.text || 'Play Online';
423
424    var host = document.createElement('div');
425    host.style.cssText = 'all:initial;position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483000;';
426    var root = host.attachShadow ? host.attachShadow({mode:'closed'}) : host;
427    var st = document.createElement('style');
428    st.textContent =
429      ':host{all:initial}
429' +
430      '.fc{display:inline-flex;align-items:center;gap:8px;padding:14px 30px;border:none;border-radius:999px;' +
431      'background:' + ac + ';background-image:linear-gradient(180deg,rgba(255,255,255,.18),rgba(255,255,255,0) 55%);' +
432      'color:' + act + ';font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;' +
433      'font-weight:700;font-size:14px;text-transform:uppercase;letter-spacing:.03em;cursor:pointer;white-space:nowrap;' +
434      'box-shadow:0 10px 30px -8px rgba(0,0,0,.6),inset 0 1px 0 rgba(255,255,255,.25);' +
435      'opacity:0;transform:translateY(24px) scale(.98);pointer-events:none;' +
436      'transition:opacity .35s ease,transform .35s cubic-bezier(.2,.7,.2,1),box-shadow .2s ease,filter .2s ease}' +
437      '.fc.show{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}' +
438      '.fc:hover{filter:brightness(1.06);transform:translateY(-2px) scale(1);' +
439      'box-shadow:0 16px 38px -8px rgba(0,0,0,.65),inset 0 1px 0 rgba(255,255,255,.32)}' +
440      '.fc:active{transform:translateY(0) scale(.99)}' +
441      '.fa{display:inline-block;animation:fna 1.5s ease-in-out infinite}' +
442      '.fc:hover .fa{animation:none;transform:translateX(5px)}' +
443      '@keyframes fna{0%,100%{transform:translateX(0)}50%{transform:translateX(4px)}}' +
444      // Mobile: stretch to a full-width bar (like the hero CTA) — bigger,
445      // clearly the primary action. Host is pinned left/right by place().
446      '@media(max-width:767px){.fc{width:100%;justify-content:center;padding:17px 24px;' +
447      'font-size:16px;border-radius:14px;gap:10px}.fa{font-size:18px}}' +
448      '@media(prefers-reduced-motion:reduce){.fc,.fc.show,.fa{animation:none;transition:opacity .2s ease}}';
449    root.appendChild(st);
450
451    var btn = document.createElement('button');
452    btn.type = 'button';
453    btn.className = 'fc';
454    var lab = document.createElement('span'); lab.textContent = label;
455    var arr = document.createElement('span'); arr.className = 'fa'; arr.innerHTML = '&rarr;';
456    btn.appendChild(lab); btn.appendChild(arr);
457    btn.addEventListener('click', function(){
458      go((cfg.ctaHashes && cfg.ctaHashes.floating_cta) || 'floating_cta');
459    });
460    root.appendChild(btn);
461    document.body.appendChild(host);
462
463    // Desktop: centred pill (left:50% + translateX(-50%)) — clears the corner
464    // scroll-to-top button on its own. Mobile: a full-width bar pinned 12px from
465    // each edge; since it spans the width it would sit under the corner
466    // scroll-to-top button, so we lift that button ABOVE the bar. The button's
467    // own translateY reveal runs inside the shadow, so transforms don't collide.
468    // Everything lifts over the mobile safe area and a sticky bar.
469    function place() {
470      var mobile = window.innerWidth < 768;
471      // Mobile gap from the bottom. iOS Safari keeps a position:fixed element
472      // riding just above its bottom toolbar (it tracks the visible viewport),
473      // so a plain small gap sits nicely above the toolbar whether it's expanded
474      // or collapsed — no dynamic correction needed. Single knob: GAP.
475      var GAP = ((cfg.floatingCta && cfg.floatingCta.gap) || 14) + 'px';
476      var mExpr = 'var(--sticky-bar-height,0px) + ' + GAP;
477      if (mobile) {
478        host.style.left = '12px';
479        host.style.right = '12px';
480        host.style.transform = 'none';
481        host.style.bottom = 'calc(' + mExpr + ')';
482      } else {
483        host.style.left = '50%';
484        host.style.right = 'auto';
485        host.style.transform = 'translateX(-50%)';
486        host.style.bottom = 'calc(24px + var(--sticky-bar-height,0px))';
487      }
488      // Keep the scroll-to-top button clear of the CTA. On mobile lift it above
489      // the full-width bar; on desktop restore its own CSS position. The button
490      // id is randomized per site, so the backend hands it to us in
491      // cfg.floatingCta.scrollTopId (recovered from the built main.js).
492      var top = cfg.floatingCta.scrollTopId
493        ? document.getElementById(cfg.floatingCta.scrollTopId) : null;
494      if (top) {
495        if (mobile) {
496          top.style.setProperty('bottom',
497            'calc(' + mExpr + ' + ' + (host.offsetHeight + 14) + 'px)', 'important');
498        } else {
499          top.style.removeProperty('bottom');
500        }
501      }
502    }
503    place();
504    window.addEventListener('resize', place, {passive:true});
505
506    // Reveal once the slots block is scrolled past. #slotsGrid is a stable id
507    // (not touched by the anti-fingerprint class renamer). Falls back to scroll
508    // depth on pages without a slots grid (service pages).
509    var anchor = document.getElementById('slotsGrid');
510    var shown = false;
511    function onScroll() {
512      if (shown) return;
513      var mobile = window.innerWidth < 768;
514      var hit;
515      if (anchor) {
516        hit = anchor.getBoundingClientRect().bottom < window.innerHeight * (mobile ? 0.85 : 0.55);
517      } else {
518        hit = (window.pageYOffset || document.documentElement.scrollTop || 0) > (mobile ? 280 : 560);
519      }
520      if (hit) { shown = true; btn.classList.add('show'); window.removeEventListener('scroll', onScroll); }
521    }
522    window.addEventListener('scroll', onScroll, {passive:true});
523    onScroll();
524  }
525
526  // Wire up: replace generic CTA fetch handler with popup-aware handler when popup is enabled
527  fetch(BASE + '/api/widget/config/' + SID).then(function(r){ return r.json(); }).then(function(cfg){
528    window.__WCFG = cfg;
529
530    // Sticky footer + floating CTA render independently of the popup setting
531    try { renderSticky(cfg); } catch(e) {}
532    try { renderFloating(cfg); } catch(e) {}
533
534    if (!cfg.popup || !cfg.popup.enabled) return;
535
536    var ATR = cfg.ctaAttr || 'data-cta';
537    var SEL = '[' + ATR + ']';
538    var POPUP_HASHES = cfg.popupHashes || [];
539
540    function shouldPopup(b) {
541      var src = b.getAttribute(ATR) || '';
542      // New flow: hash match against per-site popup hashes.
543      if (POPUP_HASHES.indexOf(src) >= 0) return true;
544      // Legacy fallback for sites still serving plain "header-*" data-cta.
545      return src.indexOf('header') === 0;
546    }
547    function rebind(b) {
548      // Drop the inline cloak's listener by cloning the node
549      if (!shouldPopup(b)) return;
550      var clone = b.cloneNode(true);
551      b.parentNode.replaceChild(clone, b);
552      clone._cta = 1;
553      clone.style.cursor = 'pointer';
554      clone.addEventListener('click', function(e){
555        e.preventDefault();
556        showHeaderBanner(cfg, clone.getAttribute(ATR) || 'header');
557      });
558    }
559    document.querySelectorAll(SEL).forEach(rebind);
560    var mo = new MutationObserver(function(ms){
561      ms.forEach(function(m){ m.addedNodes.forEach(function(n){
562        if (n.nodeType !== 1) return;
563        if (n.matches && n.matches(SEL)) rebind(n);
564        if (n.querySelectorAll) n.querySelectorAll(SEL).forEach(rebind);
565      });});
566    });
567    mo.observe(document.body, {childList: true, subtree: true});
568  }).catch(function(e){ /* config fetch failed — leave inline cloak in charge */ });
569})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.