PageSourceSearch

https://assets.forbes.cz/l/2lv7956f.js

js forbes.cz collected 2026-10-02 02:58:13 UTC 80,741 bytes, 1,707 lines download raw bytes

1
2(function(){
3
4  // Create a session join key early so sync boot can include it
5  var __qaxal_client_session_id = (function(){
6    try {
7      var key = "_sid";
8      var legacyKey = "_qaxal_sid";
9      
10      // 1. Read all sources
11      var cookieVal = (document.cookie.match(new RegExp('(^|;)\s*' + key + '\s*=\s*([^;]+)')) || [])[2];
12      var storageVal = sessionStorage.getItem(key);
13      var legacyVal = sessionStorage.getItem(legacyKey);
14      
15      // 2. Resolve Single Source of Truth (Cookie > Storage > Legacy > New)
16      var sid = cookieVal || storageVal || legacyVal || 
17               (crypto.randomUUID ? crypto.randomUUID() : (Date.now()+"."+Math.random().toString(16).slice(2)));
18
19      // 3. Sync EVERYWHERE
20      sessionStorage.setItem(key, sid);
21      
22      // Calculate root domain for cookie
23      var parts = location.hostname.split('.');
24      var rootDomain = parts.length >= 2 ? "." + parts.slice(-2).join('.') : ""; 
25      var domainAttr = rootDomain ? "; domain=" + rootDomain : "";
26      
27      // Set _sid cookie (Lax, Secure, Root Domain)
28      document.cookie = key + "=" + sid + "; path=/; SameSite=Lax; Secure" + domainAttr;
29      
30      // 4. Cleanup Legacy
31      if (legacyVal) sessionStorage.removeItem(legacyKey);
32
33      return sid;
34    } catch(e) {
35      return null;
36    }
37  })();
38
39  // EARLY PROXY INTERCEPTOR INSTALL
40  // Ensures first analytics hits are routed via /k/:token?p=<base64>
41  try {
42    if ("44sf49pu" && "44sf49pu".length > 0) {
43      setupProxyInterceptors({
44        container_token: "44sf49pu",
45        sgtm_url: location.origin,
46        base_path: ""
47      });
48    }
49  } catch (e) {
50    // fail silently – never block page execution
51  }
52  function fireRestore(containerToken, baseUrl) {
53    if (!containerToken) return Promise.resolve();
54
55    // Consent signal for the server-set _fpid gate. The page URL almost never
56    // carries gcs, so relying on it alone meant the router fell back to the
57    // qaxal_consent cookie — which tenants on a third-party CMP (Cookiebot &
58    // co.) do not have, so it defaulted to DENY and the anchor was never set.
59    // consentGcs() reads the live Consent Mode state instead; the URL value
60    // still wins when present.
61    var gcs = "";
62    try {
63        var m = location.search.match(/[?&]gcs=([^&]+)/);
64        if (m && m[1]) gcs = m[1];
65    } catch(e) {}
66    if (!gcs) { try { gcs = consentGcs(); } catch(e) {} }
67
68    // Use provided base URL or fallback to relative (which might be wrong if cross-origin)
69    var prefix = baseUrl ? baseUrl : "";
70    // Remove trailing slash if present
71    if (prefix && prefix.slice(-1) === "/") prefix = prefix.slice(0, -1);
72
73    var url = prefix + "/k/" + containerToken + "/r";
74    var q = [];
75    if (gcs) q.push("gcs=" + encodeURIComponent(gcs));
76    // Hand back the localStorage mirror so the server can re-adopt the same
77    // anchor after Safari evicted the cookie (LS outlives cookie eviction).
78    try {
79      var lsFp = readFpid();
80      if (lsFp && lsFp.indexOf("FPID2.") !== 0) q.push("fp=" + encodeURIComponent(lsFp));
81    } catch(e) {}
82    if (q.length) url += "?" + q.join("&");
83    
84    if (window.fetch) {
85        return fetch(url, { method: "POST", credentials: "include" })
86                 .then(function(r){ return r.text(); }) // consume body
87                 .catch(function(){});
88    }
89    return Promise.resolve();
90  }
91
92  function afterBoot(data) {
93    if (!data) return;
94
95    // 1) Persist sgtm url for debugging/visibility
96    if (data.sgtm_url) {
97        window.qaxal_sgtm_url = data.sgtm_url;
98    }
99  
100    // 2) dataLayer markers (keeps current behavior)
101    try {
102      window.dataLayer = window.dataLayer || [];
103      if (!window.__qaxal_gtm_bootstrap_done) {
104        window.__qaxal_gtm_bootstrap_done = true;
105        window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
106      }
107      window.dataLayer.push({
108        event: "qaxal_boot",
109        qaxal_fpid: (data && (data.fpid || data.cid)) || null,
110        qaxal_profile_id: data.profile_id || null,
111        qaxal_server_session_id: data.server_session_id || null,
112        qaxal_client_session_id: __qaxal_client_session_id || null,
113        // Server-side signals (HTTP metadata, no consent needed)
114        qaxal_geo_country: data.geo_country || null,
115        qaxal_geo_city: data.geo_city || null,
116        qaxal_geo_region: data.geo_region || null,
117        qaxal_ua_browser: data.ua_browser || null,
118        qaxal_ua_device: data.ua_device || null,
119        qaxal_ua_os: data.ua_os || null
120      });
121    } catch(e){}
122
123    // 3) Install proxy interceptors (MUST run even when sync boot ran)
124    try { setupProxyInterceptors(data); } catch(e){}
125  
126    // 4) Inject GTM container script (Delayed by Restore)
127    if (!window.__qaxal_container_injected && data.container_token) {
128      window.__qaxal_container_injected = true;
129      
130      // Pass sgtm_url to ensure we hit the correct tracking domain, not the site domain
131      fireRestore(data.container_token, data.sgtm_url + (data.base_path || "")).then(function() {
132        try {
133          if (data.container_token) {
134            var s = document.createElement("script");
135            s.async = true;
136      
137            var qs = location.search || "";
138            var previewSuffix = "";
139            if (
140              qs.indexOf("gtm_debug") !== -1 ||
141              qs.indexOf("gtm_preview") !== -1 ||
142              qs.indexOf("gtm_auth") !== -1 ||
143              qs.indexOf("gtm_cookies_win") !== -1
144            ) {
145              previewSuffix = qs;
146            }
147      
148            s.src = "https://assets.forbes.cz/c/" + data.container_token + ".js" + previewSuffix + (previewSuffix ? "&" : "?") + "xsid=" + encodeURIComponent(__qaxal_client_session_id || "");
149            document.head.appendChild(s);
150          }
151        } catch(e){}
152      });
153    }
154
155    // 5) Late restore: the first boot ran with no _fpid, so cookie-keeper restore
156    // (server-keyed on _fpid) was a no-op. Now that consent minted an _fpid, run
157    // restore exactly once. GTM is NOT re-injected (guarded above).
158    if (window.__qaxal_boot_had_no_fpid && !window.__qaxal_restore_fpid_done
159        && data.container_token && readFpid()) {
160      window.__qaxal_restore_fpid_done = true;
161      fireRestore(data.container_token, data.sgtm_url + (data.base_path || ""));
162    }
163  }
164
165    function getCookie(name){
166    var m = document.cookie.match(new RegExp("(?:^|;\s*)" + name + "=([^;]+)"));
167    return m ? decodeURIComponent(m[1]) : null;
168  }
169  function setCookie(name, value, options){
170    document.cookie = name + "=" + encodeURIComponent(value) + ";" + options;
171  }
172  // =====================================================
173  // Consent-aware FPID (ePrivacy Art 5(3) / SK §109)
174  // FPID is the marketing/attribution anchor. It must NOT be minted or
175  // persisted for a NEW visitor before consent. A RETURNING already-consented
176  // visitor's existing _fpid may still be READ pre-consent (it exists lawfully).
177  // "consent present" mirrors the router: statistics OR marketing granted.
178  // _sid (technical session id) is untouched and never linked to fpid.
179  // =====================================================
180  var FPID_COOKIE = "_fpid", FPID_LS = "_fpid", FPID_LEGACY = "qaxal_fpid";
181
182  function qaxalReadConsent(){
183    try {
184      var m = document.cookie.match(/qaxal_consent=([^;]+)/);
185      if (!m) return null;
186      var val = decodeURIComponent(m[1]);
187      if (val === "granted")  return { necessary:true, preferences:true,  statistics:true,  marketing:true  };
188      if (val === "declined") return { necessary:true, preferences:false, statistics:false, marketing:false };
189      return JSON.parse(val); // { necessary, preferences, statistics, marketing }
190    } catch(e){ return null; }
191  }
192  // Consent is read from Google Consent Mode — the signal EVERY CMP feeds
193  // (Cookiebot, OneTrust, qaxal, …), NOT from the qaxal banner specifically.
194  // This is the same signal (gcs) the router already gates on. Granted if
195  // analytics OR ad storage is granted. qaxal_consent stays only as a fallback
196  // for clients that use the qaxal banner without Consent Mode.
197  function consentState(){
198    try {
199      var ics = window.google_tag_data && window.google_tag_data.ics && window.google_tag_data.ics.entries;
200      if (ics) {
201        var a = ics.analytics_storage, m = ics.ad_storage;
202        var ag = a && a.update;
203        var mg = m && m.update;
204        var hasUpdate = (a && a.update !== undefined) || (m && m.update !== undefined);
205        var granted = function(v){ return v === true || v === "granted"; };
206        if (hasUpdate) return (granted(ag) || granted(mg)) ? "granted" : "denied";
207      }
208    } catch (_) {}
209    var c = qaxalReadConsent();
210    if (!c) return "pending";
211    return (c.statistics === true || c.marketing === true) ? "granted" : "denied";
212  }
213  function consentPresent(){
214    return consentState() === "granted";
215  }
216
217  // Live consent state as a Consent Mode "gcs" string, for the server-side gate
218  // on /r. Format matches what the router parses: "G1" + ad_storage +
219  // analytics_storage. Returns "" when no signal exists at all, in which case
220  // the router falls back to the qaxal_consent cookie and otherwise denies.
221  function consentGcs(){
222    var m = null, a = null;
223    try {
224      var ics = window.google_tag_data && window.google_tag_data.ics && window.google_tag_data.ics.entries;
225      if (ics) {
226        var g = function(e){
227          if (!e) return null;
228          var v = (e.update !== undefined ? e.update : e.default);
229          if (v === undefined) return null;
230          return (v === true || v === "granted") ? 1 : 0;
231        };
232        a = g(ics.analytics_storage); m = g(ics.ad_storage);
233      }
234    } catch (_) {}
235    if (m === null && a === null) {
236      var c = qaxalReadConsent();
237      if (!c) return "";
238      m = (c.marketing === true) ? 1 : 0;
239      a = (c.statistics === true) ? 1 : 0;
240    }
241    return "G1" + (m === null ? 0 : m) + (a === null ? 0 : a);
242  }
243
244  // READ-ONLY: returns an existing fpid, never mints, never writes. Always lawful.
245  function readFpid(){
246    var v = getCookie(FPID_COOKIE);
247    if (v && v.indexOf("FPID2.") === 0) v = null;
248    if (!v) { try { v = localStorage.getItem(FPID_LS); } catch(e){} if (v && v.indexOf("FPID2.") === 0) v = null; }
249    if (!v) { try { v = localStorage.getItem(FPID_LEGACY); } catch(e){} }
250    return v || null;
251  }
252
253  // MINT + persist — hard-guarded by consentPresent() (defense in depth).
254  //
255  // The cookie IS written here. A previous version moved the write to the router
256  // (/r) so the anchor would be server-set and escape Safari's 7-day cap on
257  // script-written cookies. That dropped cookie restoration by ~95% overnight
258  // (~145k/day to ~5k/day) because the whole cookie-keeper is keyed on _fpid:
259  // /r fires once, from afterBoot, at a point where a third-party CMP often has
260  // not published Consent Mode yet, so the router saw no consent and set
261  // nothing — and the late-consent path could not recover, since it is gated on
262  // readFpid() being non-null, which it never was without this write.
263  //
264  // The server-side write on /r is kept, but as a REFRESH on top of this one
265  // rather than a replacement: whenever consent does reach the router it re-sets
266  // the same value server-side, which restores the full lifetime. Worst case the
267  // anchor is script-written and capped; best case the server upgrades it. It is
268  // never absent.
269  //
270  // Trade-off taken deliberately: the ITP benefit was never observed in our own
271  // data (no 7-day cliff in the fpid survival curve; a JS-written probe kept its
272  // full expiry in both macOS and iOS Safari), while the restoration loss was
273  // measured and severe.
274  function mintFpid(){
275    if (!consentPresent()) return null;
276    var parts = location.hostname.split('.');
277    var rootDomain = parts.length >= 2 ? "." + parts.slice(-2).join('.') : "";
278    var domainAttr = rootDomain ? "; domain=" + rootDomain : "";
279
280    var fpid = readFpid();
281    if (!fpid) {
282      fpid = (crypto.randomUUID ? crypto.randomUUID() : (Date.now()+"."+Math.random().toString(16).slice(2)));
283    }
284    try { localStorage.setItem(FPID_LS, fpid); } catch(e){}
285    try { if (localStorage.getItem(FPID_LEGACY)) localStorage.removeItem(FPID_LEGACY); } catch(e){}
286
287    // Remove any stale HOST-ONLY _fpid first: a host-only cookie (e.g. on
288    // www.forbes.sk) shadows the shared root-domain cookie and makes each
289    // subdomain read a different value → split identity across www ↔ checkout
290    // subdomain. Then set ONE clean root-domain cookie (mirrors how _sid is set),
291    // shared across all *.rootdomain subdomains. No host-only fallback.
292    if (rootDomain) {
293      document.cookie = FPID_COOKIE + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
294    }
295    document.cookie = FPID_COOKIE + "=" + encodeURIComponent(fpid) + "; Path=/; Secure; SameSite=Lax; Max-Age=63072000" + domainAttr;
296    return fpid;
297  }
298
299  // Never expose the marketing anchor without consent, even when a stale value
300  // already exists in localStorage or a cookie.
301  function resolveFpidForBoot(){
302    return consentPresent() ? mintFpid() : null;
303  }
304
305  // Active purge on consent withdrawal: erase the FPID marketing anchor from
306  // cookie (root-domain + host-only) and localStorage. Called only on an
307  // explicit deny (not on "no decision yet").
308  function purgeFpid(){
309    try { localStorage.removeItem(FPID_LS); } catch(e){}
310    try { localStorage.removeItem(FPID_LEGACY); } catch(e){}
311    var parts = location.hostname.split('.');
312    var rootDomain = parts.length >= 2 ? "." + parts.slice(-2).join('.') : "";
313    var domainAttr = rootDomain ? "; domain=" + rootDomain : "";
314    document.cookie = FPID_COOKIE + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/" + domainAttr;
315    document.cookie = FPID_COOKIE + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
316    document.cookie = FPID_LEGACY + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/" + domainAttr;
317  }
318
319
320
321
322  // =====================================================
323  // Qaxal Consent Bar (module key: consent_bar)
324  // The banner is the Consent Mode v2 PRODUCER; the consent engine above
325  // (qaxalReadConsent / consentPresent / FPID lifecycle) stays the consumer.
326  // Zero-footprint guarantee: when the module is disabled the serve-time cfg
327  // is null and this block is fully inert — no DOM, no listeners, no cookie
328  // writes, no network calls.
329  // Compliance guardrails, hard-coded: no pre-checked categories, Necessary
330  // locked on, refusal remembered (no re-ask within refusal_ttl_days),
331  // X-close = no consent (disclosed in copy), withdrawal via persistent
332  // widget or window.QaxalConsent.open(), layer 2 ALWAYS carries Reject all.
333  // Layer 1 shows equal-prominence Accept/Reject by default; the tenant can
334  // opt out with show_reject_all:false (their legal call — documented as
335  // failing the CNIL/AEPD/Garante/DSK equal-prominence baseline).
336  // custom_ui:true = headless mode: no built-in UI at all, the tenant renders
337  // their own against the window.QaxalConsent API + qaxal:consent-* events;
338  // cookie/Consent Mode/FPID plumbing stays ours.
339  // =====================================================
340  var __qaxal_consent_cfg = null;
341  (function(){
342    var cfg = __qaxal_consent_cfg;
343    if (!cfg || !cfg._enabled) return;
344    if (cfg._geo_applies === false) return;
345
346    var VER = cfg.version || 1;
347    var CUSTOM_UI = cfg.custom_ui === true;
348    var isPreview = location.search.indexOf('_qaxal_consent_preview') !== -1;
349    var TTL_GRANT_S = (cfg.consent_ttl_days || 365) * 86400;
350    var TTL_DENY_S = (cfg.refusal_ttl_days || 182) * 86400;
351
352    var I18N = {
353      en: { title:'We value your privacy', text:'We use necessary cookies to run this site. With your consent we also use cookies to measure traffic and personalize marketing. You can change your choice at any time.', accept:'Accept all', reject:'Reject all', settings:'Settings', save:'Save selection', back:'Back', policy:'Privacy policy', widget:'Cookie settings', close_note:'Closing keeps optional cookies off.', cat_necessary:'Necessary', cat_necessary_d:'Required for the site to work (security, basic features, storing this choice). Always on.', cat_preferences:'Preferences', cat_preferences_d:'Remembers choices like language or region.', cat_statistics:'Statistics', cat_statistics_d:'Anonymous usage measurement that helps us improve the site.', cat_marketing:'Marketing', cat_marketing_d:'Used to measure and personalize advertising.' },
354      sk: { title:'Vážime si vaše súkromie', text:'Na prevádzku stránky používame nevyhnutné cookies. S vaším súhlasom ich používame aj na meranie návštevnosti a personalizáciu marketingu. Svoju voľbu môžete kedykoľvek zmeniť.', accept:'Prijať všetko', reject:'Odmietnuť všetko', settings:'Nastavenia', save:'Uložiť výber', back:'Späť', policy:'Zásady ochrany osobných údajov', widget:'Nastavenia cookies', close_note:'Zatvorením ostanú voliteľné cookies vypnuté.', cat_necessary:'Nevyhnutné', cat_necessary_d:'Potrebné pre fungovanie stránky (bezpečnosť, základné funkcie, uloženie tejto voľby). Vždy zapnuté.', cat_preferences:'Preferencie', cat_preferences_d:'Zapamätanie volieb ako jazyk alebo región.', cat_statistics:'Štatistika', cat_statistics_d:'Anonymné meranie návštevnosti, ktoré nám pomáha stránku zlepšovať.', cat_marketing:'Marketing', cat_marketing_d:'Meranie a personalizácia reklamy.' },
355      cs: { title:'Vážíme si vašeho soukromí', text:'K provozu stránky používáme nezbytné cookies. S vaším souhlasem je používáme i k měření návštěvnosti a personalizaci marketingu. Svou volbu můžete kdykoli změnit.', accept:'Přijmout vše', reject:'Odmítnout vše', settings:'Nastavení', save:'Uložit výběr', back:'Zpět', policy:'Zásady ochrany osobních údajů', widget:'Nastavení cookies', close_note:'Zavřením zůstanou volitelné cookies vypnuté.', cat_necessary:'Nezbytné', cat_necessary_d:'Potřebné pro fungování stránky (bezpečnost, základní funkce, uložení této volby). Vždy zapnuto.', cat_preferences:'Preference', cat_preferences_d:'Zapamatování voleb jako jazyk nebo region.', cat_statistics:'Statistika', cat_statistics_d:'Anonymní měření návštěvnosti, které nám pomáhá web zlepšovat.', cat_marketing:'Marketing', cat_marketing_d:'Měření a personalizace reklamy.' },
356      de: { title:'Wir respektieren Ihre Privatsphäre', text:'Wir verwenden notwendige Cookies für den Betrieb der Website. Mit Ihrer Einwilligung nutzen wir Cookies auch zur Reichweitenmessung und Marketing-Personalisierung. Sie können Ihre Wahl jederzeit ändern.', accept:'Alle akzeptieren', reject:'Alle ablehnen', settings:'Einstellungen', save:'Auswahl speichern', back:'Zurück', policy:'Datenschutzerklärung', widget:'Cookie-Einstellungen', close_note:'Beim Schließen bleiben optionale Cookies deaktiviert.', cat_necessary:'Notwendig', cat_necessary_d:'Erforderlich für den Betrieb der Website (Sicherheit, Grundfunktionen, Speicherung dieser Wahl). Immer aktiv.', cat_preferences:'Präferenzen', cat_preferences_d:'Speichert Einstellungen wie Sprache oder Region.', cat_statistics:'Statistik', cat_statistics_d:'Anonyme Nutzungsmessung zur Verbesserung der Website.', cat_marketing:'Marketing', cat_marketing_d:'Messung und Personalisierung von Werbung.' }
357    };
358    function pickLang(){
359      var avail = {};
360      var k;
361      for (k in I18N) avail[k] = 1;
362      if (cfg.languages) { for (k in cfg.languages) avail[k] = 1; }
363      var cands = [];
364      try { if (document.documentElement.lang) cands.push(document.documentElement.lang.slice(0,2).toLowerCase()); } catch(e){}
365      try { if (navigator.language) cands.push(navigator.language.slice(0,2).toLowerCase()); } catch(e){}
366      for (var i = 0; i < cands.length; i++) if (avail[cands[i]]) return cands[i];
367      return cfg.default_language && avail[cfg.default_language] ? cfg.default_language : 'en';
368    }
369    var LANG = pickLang();
370    function t(key){
371      var ov = cfg.languages && cfg.languages[LANG];
372      if (ov && typeof ov[key] === 'string' && ov[key]) return ov[key];
373      var base = I18N[LANG] || I18N.en;
374      return base[key] || I18N.en[key] || key;
375    }
376
377    var OPTIONAL_CATS = ['preferences','statistics','marketing'].filter(function(c){
378      return !(cfg.categories && cfg.categories[c] && cfg.categories[c].enabled === false);
379    });
380
381    function gtagPush(){ (window.dataLayer = window.dataLayer || []).push(arguments); }
382    // DOM events for custom_ui builds (and anyone else listening):
383    // qaxal:consent-prompt (a choice is required) / qaxal:consent-change
384    // (a choice was applied). Distinct from the dataLayer event
385    // qaxal_consent_update, which stays for GTM triggers.
386    function fireEvent(name, detail){
387      try { document.dispatchEvent(new CustomEvent(name, { detail: detail })); } catch(e){}
388    }
389    function signalsFor(c){
390      var g = function(b){ return b ? 'granted' : 'denied'; };
391      return {
392        ad_storage: g(c.marketing), ad_user_data: g(c.marketing), ad_personalization: g(c.marketing),
393        analytics_storage: g(c.statistics),
394        functionality_storage: g(c.preferences), personalization_storage: g(c.preferences),
395        security_storage: 'granted'
396      };
397    }
398    function readStored(){
399      var c = qaxalReadConsent();
400      return (c && typeof c === 'object') ? c : null;
401    }
402    function rootDomainAttr(){
403      var parts = location.hostname.split('.');
404      var rd = parts.length >= 2 ? '.' + parts.slice(-2).join('.') : '';
405      return rd ? '; domain=' + rd : '';
406    }
407
408    // ---- Consent Mode v2 producer ----
409    // Defaults MUST precede the GTM container. The loader injects GTM only
410    // after /boot resolves, so this synchronous block always runs first.
411    var stored = readStored();
412    var dflt = signalsFor({ preferences:false, statistics:false, marketing:false });
413    dflt.wait_for_update = (cfg.consent_mode && cfg.consent_mode.wait_for_update_ms) || 500;
414    gtagPush('consent', 'default', dflt);
415    if (cfg.consent_mode && cfg.consent_mode.url_passthrough) gtagPush('set', 'url_passthrough', true);
416    if (!(cfg.consent_mode && cfg.consent_mode.ads_data_redaction === false)) gtagPush('set', 'ads_data_redaction', true);
417    if (stored) gtagPush('consent', 'update', signalsFor(stored));
418
419    function writeCookie(c){
420      var payload = { necessary:true, preferences:!!c.preferences, statistics:!!c.statistics, marketing:!!c.marketing, ts: Date.now(), v: VER };
421      var anyGrant = payload.preferences || payload.statistics || payload.marketing;
422      var ttl = anyGrant ? TTL_GRANT_S : TTL_DENY_S;
423      document.cookie = 'qaxal_consent=' + encodeURIComponent(JSON.stringify(payload)) + '; Path=/; Max-Age=' + ttl + '; SameSite=Lax; Secure' + rootDomainAttr();
424      return payload;
425    }
426    // HTTP re-set of the same cookie: server-set first-party cookies keep their
427    // full Max-Age under Safari ITP (JS-set are capped at 7 days). This request
428    // is also the future consent-event log hook (see recordConsentEvent()).
429    function persistServerSide(payload){
430      try {
431        fetch('https://assets.forbes.cz/qaxal/consent', {
432          method: 'POST', keepalive: true, credentials: 'include',
433          headers: { 'content-type': 'application/json' },
434          body: JSON.stringify({ loader_token: '2lv7956f', sid: getClientSessionId(), consent: payload, lang: LANG })
435        }).catch(function(){});
436      } catch(e){}
437    }
438
439    // ---- UI ----
440    var host = null, shadowRoot = null, prevFocus = null;
441    function esc(fn){ return function(ev){ try { fn(ev); } catch(e){} }; }
442    function el(tag, cls, text){
443      var n = document.createElement(tag);
444      if (cls) n.className = cls;
445      if (text) n.textContent = text; // textContent ONLY — tenant strings are never parsed as HTML
446      return n;
447    }
448    function hexA(hex, a){
449      var h = String(hex || '').replace('#', '');
450      if (h.length === 3) h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2);
451      var n = parseInt(h, 16);
452      if (isNaN(n) || h.length !== 6) return 'rgba(39,10,255,' + a + ')';
453      return 'rgba(' + ((n>>16)&255) + ',' + ((n>>8)&255) + ',' + (n&255) + ',' + a + ')';
454    }
455    function baseCss(){
456      var th = cfg.theme || {};
457      var accent = th.accent || '#270aff';
458      var bg = th.bg || '#ffffff';
459      var text = th.text || '#111318';
460      var radius = (th.btn_radius_px != null ? th.btn_radius_px : 12) + 'px';
461      var panelR = (th.panel_radius_px != null ? th.panel_radius_px : 20) + 'px';
462      var shadowOn = th.shadow !== false;
463      var panelShadow = shadowOn ? '0 24px 64px -16px rgba(8,10,24,.28),0 4px 16px rgba(8,10,24,.08)' : 'none';
464      var widgetShadow = shadowOn ? '0 4px 20px rgba(8,10,24,.14)' : 'none';
465      var widgetShadowHover = shadowOn ? '0 6px 24px rgba(8,10,24,.22)' : 'none';
466      var font = th.font_family || 'system-ui, -apple-system, Segoe UI, Roboto, sans-serif';
467      return ':host{all:initial}' +
468        '.qcb-wrap{position:fixed;z-index:2147483646;font-family:' + font + ';color:' + text + ';line-height:1.5;font-size:14px;-webkit-font-smoothing:antialiased}' +
469        '.qcb-panel{position:relative;background:' + bg + ';box-shadow:' + panelShadow + ';border:1px solid ' + hexA(text, .06) + ';border-radius:' + panelR + ';padding:24px;box-sizing:border-box;max-height:min(85vh,720px);overflow-y:auto}' +
470        '.qcb-dialog{inset:0;display:flex;align-items:center;justify-content:center;padding:16px}' +
471        '.qcb-dialog .qcb-panel{max-width:440px;width:100%}' +
472        '.qcb-overlay{position:fixed;inset:0;z-index:2147483645;background:rgba(10,12,24,.45);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}' +
473        '.qcb-corner-left{left:20px;bottom:20px;max-width:380px}' +
474        '.qcb-corner-right{right:20px;bottom:20px;max-width:380px}' +
475        '.qcb-bar-top{top:0;left:0;right:0}.qcb-bar-top .qcb-panel{border-radius:0 0 ' + panelR + ' ' + panelR + '}' +
476        '.qcb-bar-bottom{bottom:0;left:0;right:0}.qcb-bar-bottom .qcb-panel{border-radius:' + panelR + ' ' + panelR + ' 0 0}' +
477        '.qcb-bar-top .qcb-panel,.qcb-bar-bottom .qcb-panel{max-width:none;display:flex;flex-wrap:wrap;gap:16px;align-items:center;justify-content:space-between}' +
478        '.qcb-bar-top .qcb-copy,.qcb-bar-bottom .qcb-copy{flex:1 1 320px;min-width:260px}' +
479        '.qcb-bar-top .qcb-btns,.qcb-bar-bottom .qcb-btns{margin-top:0;flex:0 1 auto}' +
480        '.qcb-title{font-weight:700;font-size:16px;margin:0 0 8px;letter-spacing:-.01em;padding-right:36px}' +
481        '.qcb-text{margin:0;font-size:14px;color:' + hexA(text, .75) + '}' +
482        '.qcb-plink{color:' + accent + ';text-decoration:none;font-weight:500;cursor:pointer}' +
483        '.qcb-plink:hover{text-decoration:underline}' +
484        '.qcb-btns{display:flex;flex-wrap:wrap;gap:10px;margin-top:20px}' +
485        '.qcb-btn{cursor:pointer;font:inherit;font-size:14px;font-weight:600;line-height:1.25;padding:11px 12px;border-radius:' + radius + ';border:none;flex:1 1 calc(50% - 5px);min-width:0;text-align:center;transition:filter .15s ease,background-color .15s ease,transform .06s ease}' +
486        '.qcb-btns .qcb-btn-ghost{flex:1 1 100%}' +
487        '.qcb-btns-noreject .qcb-btn-ghost{flex:1 1 calc(50% - 5px)}' +
488        '.qcb-bar-top .qcb-btn,.qcb-bar-bottom .qcb-btn{flex:0 1 auto;min-width:150px}' +
489        '.qcb-bar-top .qcb-btns .qcb-btn-ghost,.qcb-bar-bottom .qcb-btns .qcb-btn-ghost{flex:0 1 auto}' +
490        '.qcb-btn:active{transform:scale(.98)}' +
491        '.qcb-btn-primary{background:' + accent + ';color:#fff}' +
492        '.qcb-btn-primary:hover{filter:brightness(1.12)}' +
493        '.qcb-btn-ghost{background:' + hexA(accent, .08) + ';color:' + accent + '}' +
494        '.qcb-btn-ghost:hover{background:' + hexA(accent, .14) + '}' +
495        '.qcb-btn:focus-visible,.qcb-plink:focus-visible,.qcb-x:focus-visible,.qcb-widget:focus-visible{outline:3px solid ' + hexA(accent, .5) + ';outline-offset:2px}' +
496        '.qcb-x{position:absolute;top:14px;right:14px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:' + hexA(text, .06) + ';border:none;border-radius:999px;font-size:15px;line-height:1;cursor:pointer;color:' + text + ';opacity:.75;transition:opacity .15s ease}' +
497        '.qcb-x:hover{opacity:1}' +
498        '.qcb-cats{margin-top:14px}' +
499        '.qcb-row{display:flex;gap:14px;align-items:center;justify-content:space-between;padding:13px 0;border-top:1px solid ' + hexA(text, .07) + '}' +
500        '.qcb-row:first-child{border-top:none}' +
501        '.qcb-cat-t{font-weight:600;margin:0;font-size:14px}' +
502        '.qcb-cat-d{margin:2px 0 0;font-size:12.5px;color:' + hexA(text, .6) + '}' +
503        '.qcb-sw{position:relative;flex:none;width:44px;height:26px;
503display:inline-block}' +
504        '.qcb-sw input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}' +
505        '.qcb-sw input:disabled{cursor:default}' +
506        '.qcb-knob{position:absolute;inset:0;background:' + hexA(text, .18) + ';border-radius:999px;transition:background-color .18s ease;pointer-events:none}' +
507        '.qcb-knob:after{content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;background:#fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:transform .18s ease}' +
508        '.qcb-sw input:checked + .qcb-knob{background:' + accent + '}' +
509        '.qcb-sw input:checked + .qcb-knob:after{transform:translateX(18px)}' +
510        '.qcb-sw input:disabled + .qcb-knob{opacity:.45}' +
511        '.qcb-sw input:focus-visible + .qcb-knob{outline:3px solid ' + hexA(accent, .5) + ';outline-offset:2px}' +
512        '.qcb-note{font-size:12px;color:' + hexA(text, .5) + ';margin:12px 0 0}' +
513        '.qcb-foot{font-size:12.5px;margin:12px 0 0}' +
514        '.qcb-foot .qcb-plink{font-weight:500}' +
515        '.qcb-bar-top .qcb-foot,.qcb-bar-bottom .qcb-foot,.qcb-bar-top .qcb-note,.qcb-bar-bottom .qcb-note{margin-top:8px}' +
516        '.qcb-widget{position:fixed;z-index:2147483646;width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:' + bg + ';color:' + text + ';border:1px solid ' + hexA(text, .12) + ';border-radius:999px;padding:0;font-family:' + font + ';cursor:pointer;box-shadow:' + widgetShadow + ';transition:box-shadow .15s ease,transform .1s ease}' +
517        '.qcb-widget:hover{box-shadow:' + widgetShadowHover + ';transform:scale(1.05)}' +
518        '.qcb-widget svg{display:block}' +
519        '.qcb-widget-bl{left:20px;bottom:20px}' +
520        '.qcb-widget-br{right:20px;bottom:20px}' +
521        '@media (max-width:560px){.qcb-corner-left,.qcb-corner-right{left:12px;right:12px;bottom:12px;max-width:none}.qcb-btns{flex-direction:column}.qcb-bar-top .qcb-btns,.qcb-bar-bottom .qcb-btns{flex:1 1 100%}}' +
522        '@media (prefers-reduced-motion:no-preference){.qcb-panel{animation:qcbIn .24s cubic-bezier(.16,1,.3,1)}.qcb-overlay{animation:qcbFade .2s ease-out}}' +
523        '@keyframes qcbIn{from{opacity:0;transform:translateY(14px) scale(.98)}to{opacity:1;transform:none}}' +
524        '@keyframes qcbFade{from{opacity:0}to{opacity:1}}';
525    }
526    function mountHost(){
527      if (host) return shadowRoot;
528      host = document.createElement('div');
529      host.id = 'qaxal-consent-host';
530      shadowRoot = host.attachShadow ? host.attachShadow({ mode: 'open' }) : host;
531      var style = document.createElement('style');
532      style.textContent = baseCss();
533      shadowRoot.appendChild(style);
534      if (cfg.custom_css) {
535        var custom = document.createElement('style');
536        custom.textContent = String(cfg.custom_css);
537        shadowRoot.appendChild(custom);
538      }
539      document.documentElement.appendChild(host);
540      return shadowRoot;
541    }
542    function clearUi(){
543      if (!shadowRoot) return;
544      var keep = [];
545      for (var i = 0; i < shadowRoot.children.length; i++) {
546        var n = shadowRoot.children[i];
547        if (n.tagName === 'STYLE') keep.push(n);
548      }
549      while (shadowRoot.firstChild) shadowRoot.removeChild(shadowRoot.firstChild);
550      for (var j = 0; j < keep.length; j++) shadowRoot.appendChild(keep[j]);
551    }
552    function removeUi(){
553      if (host && host.parentNode) host.parentNode.removeChild(host);
554      host = null; shadowRoot = null;
555      if (prevFocus && prevFocus.focus) { try { prevFocus.focus(); } catch(e){} prevFocus = null; }
556    }
557    function trapFocus(panel, ev){
558      if (ev.key !== 'Tab') return;
559      var f = panel.querySelectorAll('button, a[href], input:not([disabled])');
560      if (!f.length) return;
561      var first = f[0], last = f[f.length - 1];
562      if (ev.shiftKey && shadowRoot.activeElement === first) { ev.preventDefault(); last.focus(); }
563      else if (!ev.shiftKey && shadowRoot.activeElement === last) { ev.preventDefault(); first.focus(); }
564    }
565    function layoutClass(){
566      var l = cfg.layout || 'dialog';
567      if (l === 'corner_bottom_left') return 'qcb-corner-left';
568      if (l === 'corner_bottom_right') return 'qcb-corner-right';
569      if (l === 'bar_top') return 'qcb-bar-top';
570      if (l === 'bar_bottom') return 'qcb-bar-bottom';
571      return 'qcb-dialog';
572    }
573    function policyLink(){
574      var u = cfg.privacy_policy_url;
575      if (!u || u.indexOf('http://') !== 0 && u.indexOf('https://') !== 0) return null;
576      var a = el('a', 'qcb-plink', t('policy'));
577      a.href = u; a.target = '_blank'; a.rel = 'noopener';
578      return a;
579    }
580    function dismissNoChoice(){
581      // X-close semantics: NO consent stored, optional cookies stay off,
582      // re-ask on next page load (session-quiet only).
583      try { sessionStorage.setItem('qcb_dismissed', '1'); } catch(e){}
584      removeUi();
585      mountWidget();
586    }
587    function renderLayer(layer){
588      if (CUSTOM_UI) return; // headless mode never mounts built-in UI
589      var root = mountHost();
590      clearUi();
591      var lc = layoutClass();
592      var isDialog = lc === 'qcb-dialog';
593      var isBar = lc === 'qcb-bar-top' || lc === 'qcb-bar-bottom';
594      if (isDialog && cfg.overlay !== false) root.appendChild(el('div', 'qcb-overlay'));
595      var wrap = el('div', 'qcb-wrap ' + lc);
596      var panel = el('div', 'qcb-panel');
597      panel.style.position = 'relative';
598      panel.setAttribute('role', isDialog ? 'dialog' : 'region');
599      if (isDialog) panel.setAttribute('aria-modal', 'true');
600      panel.setAttribute('aria-label', t('title'));
601
602      if (cfg.show_close_x) {
603        var x = el('button', 'qcb-x', '×');
604        x.setAttribute('aria-label', 'Close');
605        x.addEventListener('click', esc(dismissNoChoice));
606        panel.appendChild(x);
607      }
608
609      var copy = el('div', 'qcb-copy');
610      copy.appendChild(el('p', 'qcb-title', t('title')));
611      copy.appendChild(el('p', 'qcb-text', t('text')));
612      panel.appendChild(copy);
613
614      var btns = el('div', 'qcb-btns');
615      var cur = stored || {};
616
617      if (layer === 2) {
618        var boxes = {};
619        var cats = el('div', 'qcb-cats');
620        // iOS-style switch rows: label/description left, switch right.
621        var catRow = function(name, desc, checked, disabled){
622          var row = el('div', 'qcb-row');
623          var w = el('div');
624          w.appendChild(el('p', 'qcb-cat-t', name));
625          w.appendChild(el('p', 'qcb-cat-d', desc));
626          var sw = el('label', 'qcb-sw');
627          var cb = document.createElement('input');
628          cb.type = 'checkbox';
629          cb.checked = !!checked; // never pre-checked without a prior grant
630          cb.disabled = !!disabled;
631          cb.setAttribute('aria-label', name);
632          sw.appendChild(cb);
633          sw.appendChild(el('span', 'qcb-knob'));
634          row.appendChild(w); row.appendChild(sw);
635          cats.appendChild(row);
636          return cb;
637        };
638        catRow(t('cat_necessary'), t('cat_necessary_d'), true, true);
639        for (var i = 0; i < OPTIONAL_CATS.length; i++) {
640          boxes[OPTIONAL_CATS[i]] = catRow(
641            t('cat_' + OPTIONAL_CATS[i]),
642            t('cat_' + OPTIONAL_CATS[i] + '_d'),
643            cur[OPTIONAL_CATS[i]] === true,
644            false
645          );
646        }
647        copy.appendChild(cats);
648        var bReject2 = el('button', 'qcb-btn qcb-btn-primary', t('reject'));
649        bReject2.addEventListener('click', esc(function(){ applyChoice({}); }));
650        var bSave = el('button', 'qcb-btn qcb-btn-ghost', t('save'));
651        bSave.addEventListener('click', esc(function(){
652          var c = {};
653          for (var k in boxes) c[k] = !!boxes[k].checked;
654          applyChoice(c);
655        }));
656        var bAccept2 = el('button', 'qcb-btn qcb-btn-primary', t('accept'));
657        bAccept2.addEventListener('click', esc(function(){
658          var c = {};
659          for (var j = 0; j < OPTIONAL_CATS.length; j++) c[OPTIONAL_CATS[j]] = true;
660          applyChoice(c);
661        }));
662        // Pair row: Reject + Accept (equal prominence); Save wraps full-width below.
663        btns.appendChild(bReject2); btns.appendChild(bAccept2); btns.appendChild(bSave);
664      } else {
665        // Layer 1: Accept and Reject share the SAME primary style (equal
666        // prominence — CNIL/AEPD/Garante/DSK baseline). Settings is secondary.
667        // show_reject_all:false is a tenant opt-out (non-default): Reject
668        // disappears from layer 1 only — layer 2 always keeps Reject all, so
669        // refusal stays two clicks away — and Settings joins Accept in the
670        // pair row instead of wrapping full-width.
671        var bSettings = el('button', 'qcb-btn qcb-btn-ghost', t('settings'));
672        bSettings.addEventListener('click', esc(function(){ renderLayer(2); }));
673        var bAccept = el('button', 'qcb-btn qcb-btn-primary', t('accept'));
674        bAccept.addEventListener('click', esc(function(){
675          var c = {};
676          for (var j = 0; j < OPTIONAL_CATS.length; j++) c[OPTIONAL_CATS[j]] = true;
677          applyChoice(c);
678        }));
679        if (cfg.show_reject_all !== false) {
680          var bReject = el('button', 'qcb-btn qcb-btn-primary', t('reject'));
681          bReject.addEventListener('click', esc(function(){ applyChoice({}); }));
682          // Pair row: Reject + Accept (equal prominence); Settings wraps full-width below.
683          btns.appendChild(bReject); btns.appendChild(bAccept); btns.appendChild(bSettings);
684        } else {
685          btns.className += ' qcb-btns-noreject';
686          btns.appendChild(bSettings); btns.appendChild(bAccept);
687        }
688      }
689      panel.appendChild(btns);
690      // In bar layouts the panel is a flex ROW, so panel-level children would
691      // float beside the buttons; the note + policy link belong under the copy.
692      var footHost = isBar ? copy : panel;
693      if (cfg.show_close_x) footHost.appendChild(el('p', 'qcb-note', t('close_note')));
694      var pl = policyLink();
695      if (pl) {
696        var foot = el('p', 'qcb-foot');
697        foot.appendChild(pl);
698        footHost.appendChild(foot);
699      }
700
701      wrap.appendChild(panel);
702      root.appendChild(wrap);
703
704      prevFocus = prevFocus || document.activeElement;
705      panel.addEventListener('keydown', esc(function(ev){
706        if (ev.key === 'Escape' && cfg.show_close_x) dismissNoChoice();
707        if (isDialog) trapFocus(panel, ev);
708      }));
709      var firstBtn = btns.querySelector('button');
710      if (firstBtn) { try { firstBtn.focus(); } catch(e){} }
711    }
712    var widgetEl = null;
713    // Static SVG (lucide "cookie" glyph), no config-derived content — the one
714    // deliberate innerHTML in this module. currentColor inherits theme text.
715    var QCB_COOKIE_SVG = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
715<path d="M12 2a10 10 0 1 0 10 10 4 4 0 0 1-5-5 4 4 0 0 1-5-5"></path><path d="M8.5 8.5v.01"></path><path d="M16 15.5v.01"></path><path d="M12 12v.01"></path><path d="M11 17v.01"></path><path d="M7 14v.01"></path></svg>';
716    function mountWidget(){
717      if (CUSTOM_UI || cfg.floating_widget === false || widgetEl) return;
718      var root = mountHostWidget();
719      var pos = cfg.widget_position === 'bottom_right' ? 'qcb-widget-br' : 'qcb-widget-bl';
720      var b = el('button', 'qcb-widget ' + pos);
721      b.setAttribute('aria-label', t('widget'));
722      b.setAttribute('title', t('widget'));
723      b.innerHTML = QCB_COOKIE_SVG;
724      b.addEventListener('click', esc(function(){ stored = readStored(); renderLayer(2); }));
725      root.appendChild(b);
726      widgetEl = b;
727    }
728    var widgetHost = null;
729    function mountHostWidget(){
730      if (widgetHost) return widgetHost.shadowRoot || widgetHost;
731      widgetHost = document.createElement('div');
732      widgetHost.id = 'qaxal-consent-widget-host';
733      var r = widgetHost.attachShadow ? widgetHost.attachShadow({ mode: 'open' }) : widgetHost;
734      var style = document.createElement('style');
735      style.textContent = baseCss();
736      r.appendChild(style);
737      document.documentElement.appendChild(widgetHost);
738      return r;
739    }
740    function unmountWidget(){
741      if (widgetHost && widgetHost.parentNode) widgetHost.parentNode.removeChild(widgetHost);
742      widgetHost = null; widgetEl = null;
743    }
744
745    function applyChoice(c){
746      var payload = writeCookie(c);
747      stored = payload;
748      gtagPush('consent', 'update', signalsFor(payload));
749      try { window.dataLayer.push({ event: 'qaxal_consent_update', qaxal_consent: payload }); } catch(e){}
750      fireEvent('qaxal:consent-change', { consent: payload });
751      persistServerSide(payload);
752      // Deterministic same-tick FPID mint/purge + boot re-run (the consent
753      // watch would also catch this, slightly later).
754      try { onConsentMaybeChanged(); } catch(e){}
755      removeUi();
756      unmountWidget();
757      mountWidget();
758    }
759
760    // Public API for SPAs, custom footer links and custom_ui (headless)
761    // builds. Documented in docs/consent-bar.md — treat it as a stable
762    // contract once tenant UIs depend on it. accept/reject/save run the FULL
763    // pipeline (cookie, Consent Mode update, dataLayer event, POST /consent,
764    // FPID mint/purge), so a custom UI only has to render and call in.
765    window.QaxalConsent = {
766      open: function(){
767        stored = readStored();
768        if (CUSTOM_UI) { fireEvent('qaxal:consent-prompt', { reason: 'open' }); return; }
769        renderLayer(2);
770      },
771      getState: function(){ return readStored(); },
772      shouldPrompt: function(){ return shouldPrompt(); },
773      getCategories: function(){ return OPTIONAL_CATS.slice(); },
774      getLanguage: function(){ return LANG; },
775      getTexts: function(){
776        var out = {}, k;
777        for (k in I18N.en) out[k] = t(k);
778        return out;
779      },
780      acceptAll: function(){
781        var c = {}, j;
782        for (j = 0; j < OPTIONAL_CATS.length; j++) c[OPTIONAL_CATS[j]] = true;
783        applyChoice(c);
784      },
785      rejectAll: function(){ applyChoice({}); },
786      save: function(choices){
787        // Masked to enabled categories: disabled ones can never be granted.
788        var c = {}, j, cat;
789        for (j = 0; j < OPTIONAL_CATS.length; j++) {
790          cat = OPTIONAL_CATS[j];
791          c[cat] = !!(choices && choices[cat]);
792        }
793        applyChoice(c);
794      },
795      version: VER,
796      customUi: CUSTOM_UI
797    };
798
799    function shouldPrompt(){
800      if (isPreview) return true;
801      var c = readStored();
802      if (c && c.v === VER) return false;          // valid choice for current config
803      if (c && c.v !== VER) return true;           // material config change → re-ask
804      try { if (sessionStorage.getItem('qcb_dismissed') === '1') return false; } catch(e){}
805      return true;                                  // no choice yet
806    }
807    function boot(){
808      if (CUSTOM_UI) {
809        // Headless: never mounts built-in UI. Fire the prompt event for the
810        // tenant's UI; scripts that load after this point must ALSO check
811        // QaxalConsent.shouldPrompt() on their own init, since this event can
812        // fire before their listener attaches. Dismissal memory is the
813        // tenant's job here (the event re-fires every page load until a
814        // choice is stored).
815        if (shouldPrompt()) {
816          fireEvent('qaxal:consent-prompt', {
817            reason: readStored() ? 'version_change' : 'no_choice'
818          });
819        }
820        return;
821      }
822      if (shouldPrompt()) renderLayer(1);
823      else if (readStored()) mountWidget();
824    }
825    if (document.body) boot();
826    else document.addEventListener('DOMContentLoaded', esc(boot));
827  })();
828
829    function setupProxyInterceptors(data) {
830    if (!data || !data.sgtm_url || !data.container_token) return;
831  
832      (function(){
833        var containerToken = data.container_token;
834        var proxyOrigin;
835
836          try {
837            proxyOrigin = new URL(data.sgtm_url).origin;
838          } catch (e) {
839            return;
840          }
841
842          // Same-origin path mode: everything the browser sends must be anchored
843          // under this base (e.g. "/metrics") so it hits the forwarder route.
844          // Empty in subdomain mode → all logic below is a no-op (identical to
845          // before). basePath has a leading slash and no trailing slash.
846          var basePath = (data.base_path || "");
847          if (basePath && basePath.slice(-1) === "/") basePath = basePath.slice(0, -1);
848          // Strip the base off a same-origin pathname to get the canonical DIP
849          // path (what the /k/ payload and match logic expect). Leaves external
850          // (GA) pathnames untouched.
851          function stripBase(p) {
852            if (!basePath) return p;
853            if (p === basePath) return "/";
854            if (p.indexOf(basePath + "/") === 0) return p.slice(basePath.length);
855            return p;
856          }
857  
858          function encodePayload(str) {
859            try { return btoa(str); } catch (e) { return null; }
860          }
861  
862          function shouldAttachHeaders(targetUrl){
863            // IMPORTANT:
864            // Do NOT attach custom headers to /k/ because it triggers CORS preflight.
865            // We'll pass xsid via query param instead.
866            return false;
867          }
868  
869          function mergeHeaders(init){
870            var sig = getSignalHeaders();
871            init = init || {};
872            var h = init.headers;
873  
874            try {
875              if (!h) {
876                init.headers = sig;
877                return init;
878              }
879  
880              if (typeof Headers !== "undefined" && h instanceof Headers) {
881                for (var k in sig) h.set(k, sig[k]);
882                init.headers = h;
883                return init;
884              }
885  
886              if (Array.isArray(h)) {
887                for (var k2 in sig) h.push([k2, sig[k2]]);
888                init.headers = h;
889                return init;
890              }
891  
892              if (typeof h === "object") {
893                for (var k3 in sig) h[k3] = sig[k3];
894                init.headers = h;
895                return init;
896              }
897  
898              init.headers = sig;
899              return init;
900            } catch(e){
901              init.headers = sig;
902              return init;
903            }
904          }
905  
906          function rewrite(rawUrl) {
907          
908          //Logs
909          try {
910            if (window.__QAXAL_DEBUG__) {
911              console.log("[qaxal][rewrite] rawUrl =", rawUrl);
912            }
913          } catch(e){}
914
915            try {
916              var u = new URL(rawUrl, location.href);
917              var hasAb = isBlockingEnvironment();
918              var hasItp = getItpState();
919  
920              // Treat BOTH proxy origin AND page origin as proxy-eligible
921              var pageOrigin = location.origin;
922              
923              // Canonical (base-stripped) path so path-mode collect hits
924              // (/<prefix>/g/collect) still match and the /k/ payload carries
925              // the canonical path the router expects. No-op in subdomain mode.
926              var canonPath = stripBase(u.pathname);
927              var isProxyEligibleCollect =
928                (
929                  u.origin === proxyOrigin ||
930                  u.origin === pageOrigin ||
931                  u.hostname === "www.google-analytics.com" ||
932                  u.hostname === "google-analytics.com" ||
933                  u.hostname.endsWith(".analytics.google.com")
934                ) &&
935                (
936                  canonPath === "/collect" ||
937                  canonPath.indexOf("/g/collect") === 0 ||
938                  canonPath.indexOf("/r/collect") === 0
939                );
940
941              if (isProxyEligibleCollect) {
942
943              // sendBeacon-safe markers
944              if (hasAb) u.searchParams.set("xab", "1");
945              else u.searchParams.delete("xab");
946
947              if (hasItp) u.searchParams.set("xitp", "1");
948              else u.searchParams.delete("xitp");
949
950              var encodedInternal = encodePayload(canonPath + u.search);
951
952              //logs
953              try {
954                if (window.__QAXAL_DEBUG__) {
955                  console.log("[qaxal][rewrite] encoded payload =", encodedInternal);
956                }
957              } catch(e){}
958
959              if (!encodedInternal) return rawUrl;
960            
961              var proxy = new URL(basePath + "/k/" + containerToken, proxyOrigin);
962              proxy.searchParams.set("p", encodedInternal);
963            
964              // session join key without headers (avoids preflight)
965              if (__qaxal_client_session_id) proxy.searchParams.set("xsid", __qaxal_client_session_id);
966
967              //logs
968              try {
969                if (window.__QAXAL_DEBUG__) {
970                  console.log("[qaxal][rewrite] final proxy url =", proxy.toString());
971                }
972              } catch(e){}
973
974              return proxy.toString();
975            }
976  
977              if (u.hostname === "www.google.com" && u.pathname === "/ccm/collect") {
978                if (hasAb) u.searchParams.set("xab", "1");
979                else u.searchParams.delete("xab");
980  
981                if (hasItp) u.searchParams.set("xitp", "1");
982                else u.searchParams.delete("xitp");
983  
984                var encodedFull = encodePayload(u.toString());
985                if (!encodedFull) return rawUrl;
986  
987                var proxy2 = new URL(basePath + "/k/" + containerToken, proxyOrigin);
988                proxy2.searchParams.set("p", encodedFull);
989  
990                // session join key without headers (avoids preflight)
991                if (__qaxal_client_session_id) proxy2.searchParams.set("xsid", __qaxal_client_session_id);
992  
993                proxy2.__qaxal_ccm = true;
994                return proxy2.toString();
995              }
996  
997              return rawUrl;
998            } catch (e) {
999              return rawUrl;
1000            }
1001          }
1002  
1003          if (navigator && navigator.sendBeacon) {
1004            var origSB = navigator.sendBeacon.bind(navigator);
1005            navigator.sendBeacon = function(url, body) {
1006              return origSB(rewrite(url), body);
1007            };
1008          }
1009  
1010          if (window.fetch) {
1011            var origFetch = window.fetch.bind(window);
1012            window.fetch = function(resource, init) {
1013              // IMPORTANT: CCM must remain pure GET (no keepalive, no body)
1014              try {
1015                if (typeof rewritten === "string") {
1016                  var uccm = new URL(rewritten, location.href);
1017                  if (stripBase(uccm.pathname).indexOf("/k/") === 0 && rewritten.indexOf("ccm/collect") !== -1) {
1018                    return origFetch(resource, init);
1019                  }
1020                }
1021              } catch(e){}
1022              try {
1023                var rewritten;
1024  
1025                function maybeEnableKeepalive(rewrittenUrl, initObj){
1026                  try {
1027                    // Reduce "(canceled)" on navigation: allow small POSTs to finish during unload.
1028                    // Applies only to our /k/ proxy requests.
1029                    var u = new URL(rewrittenUrl, location.href);
1030                    if (u.origin === proxyOrigin && stripBase(u.pathname).indexOf("/k/") === 0) {
1031                      initObj = initObj || {};
1032                      var m = (initObj.method || "GET").toUpperCase();
1033                      if (m === "POST" && typeof initObj.keepalive === "undefined") {
1034                        initObj.keepalive = true;
1035                      }
1036                    }
1037                  } catch(e){}
1038                  return initObj;
1039                }
1040  
1041                if (typeof resource === "string") {
1042                  rewritten = rewrite(resource);
1043                  resource = rewritten;
1044  
1045                  if (shouldAttachHeaders(rewritten)) {
1046                    init = mergeHeaders(init);
1047                  }
1048  
1049                  init = maybeEnableKeepalive(rewritten, init);
1050  
1051                } else if (resource && resource.url) {
1052                  rewritten = rewrite(resource.url);
1053  
1054                  try {
1055                    var req2 = new Request(rewritten, resource);
1056                    resource = req2;
1057                  } catch(e2){
1058                    resource = rewritten;
1059                  }
1060  
1061                  if (shouldAttachHeaders(rewritten)) {
1062                    init = mergeHeaders(init);
1063                  }
1064  
1065                  // If the caller passed a Request object and no init, keepalive was not applied.
1066                  // Apply keepalive via init override so POST /k/ can finish during navigation/unload.
1067                  init = maybeEnableKeepalive(rewritten, init);
1068                  try {
1069                    var u3 = new URL(rewritten, location.href);
1070                    if (u3.origin === proxyOrigin && stripBase(u3.pathname).indexOf("/k/") === 0) {
1071                      var method3 = ((init && init.method) || (resource && resource.method) || "GET").toUpperCase();
1072                      if (method3 === "POST") {
1073                        init = init || {};
1074                        if (typeof init.keepalive === "undefined") init.keepalive = true;
1075                      }
1076                    }
1077                  } catch(e3){}
1078                }
1079              } catch(e){}
1080              return origFetch(resource, init);
1081            };
1082          }
1083  
1084          if (window.XMLHttpRequest && XMLHttpRequest.prototype.open) {
1085            var origOpen = XMLHttpRequest.prototype.open;
1086            var origSend = XMLHttpRequest.prototype.send;
1087  
1088            XMLHttpRequest.prototype.open = function(method, url) {
1089              var rewritten = url;
1090              try { rewritten = rewrite(url); } catch(e){}
1091              this.__qaxal_url = rewritten;
1092              var args = Array.prototype.slice.call(arguments);
1093              args[1] = rewritten;
1094              return origOpen.apply(this, args);
1095            };
1096  
1097            XMLHttpRequest.prototype.send = function(body) {
1098              try {
1099                if (this.__qaxal_url && shouldAttachHeaders(this.__qaxal_url)) {
1100                  var sig = getSignalHeaders();
1101                  for (var k in sig) {
1102                    try { this.setRequestHeader(k, sig[k]); } catch(e2){}
1103                  }
1104                }
1105              } catch(e){}
1106              return origSend.apply(this, arguments);
1107            };
1108          }
1109        })();
1110  }
1111
1112
1113  
1114  function getClientSessionId(){
1115    try{
1116      var key = "_sid";
1117      // Try cookie first (server visibility), then storage
1118      var match = document.cookie.match(new RegExp('(^|;)\s*' + key + '\s*=\s*([^;]+)'));
1119      var sid = match ? match[2] : sessionStorage.getItem(key);
1120      
1121      if (!sid){
1122        sid = (crypto.randomUUID ? crypto.randomUUID() : (Date.now()+"."+Math.random().toString(16).slice(2)));
1123        sessionStorage.setItem(key, sid);
1124      }
1125      
1126      // Calculate root domain (e.g. .nikub.sk) to allow sharing with data.* subdomains
1127      var parts = location.hostname.split('.');
1128      var rootDomain = parts.length >= 2 ? "." + parts.slice(-2).join('.') : ""; 
1129      var domainAttr = rootDomain ? "; domain=" + rootDomain : "";
1130
1131      // Cleanup legacy host-only cookie (prevent split session ID)
1132      if (rootDomain) {
1133         document.cookie = key + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
1134      }
1135
1136      // Ensure cookie is set (Session cookie, strict, secure, ROOT DOMAIN)
1137      document.cookie = key + "=" + sid + "; path=/; SameSite=Lax; Secure" + domainAttr;
1138      
1139      return sid;
1140    }catch(e){
1141      return null;
1142    }
1143  }
1144
1145  // --------------------------------------------------
1146  // Session-level signals (AB + ITP)
1147  //  - AB sessionStorage key: "ab"
1148  //  - ITP sessionStorage key: "itp"
1149  // --------------------------------------------------
1150  function ssGet(k){ try { return sessionStorage.getItem(k); } catch(e){ return null; } }
1151  function ssSet(k,v){ try { sessionStorage.setItem(k, v); } catch(e){} }
1152
1153  // Lightweight ITP heuristic (safe to run immediately)
1154  function detectItpHeuristic(){
1155    try {
1156      var ua = navigator.userAgent || "";
1157      var vendor = navigator.vendor || "";
1158      var isAppleVendor = vendor.indexOf("Apple") !== -1;
1159      var isSafari =
1160        isAppleVendor &&
1161        ua.indexOf("Safari") !== -1 &&
1162        ua.indexOf("Chrome") === -1 &&
1163        ua.indexOf("CriOS") === -1 &&
1164        ua.indexOf("FxiOS") === -1;
1165
1166      var isIOS = /iP(hone|ad|od)/.test(ua);
1167      var isWebKit = (ua.indexOf("AppleWebKit") !== -1) && (ua.indexOf("Gecko") === -1);
1168
1169      var likely = !!(isSafari || (isIOS && isWebKit));
1170      ssSet("itp", likely ? "1" : "0");
1171    } catch(e){
1172      ssSet("itp", "0");
1173    }
1174  }
1175
1176  function getAbState(){ return ssGet("ab"); }
1177  function setAbState(v){ ssSet("ab", v); }
1178
1179  var __qaxal_abwall = {"level":3,"dialog":{"icon":"sad","title":"Prosím, vypnite svoj adblock","body_html":"Práve vďaka našim inzerentom, môžete prezerať náš obsah bez obmedzení, preto by sme vás radi poprosili aby ste vypli svoj adblock.","body_size":13,"button_bg":"#000000","title_size":20,"action_text":"Rozumiem, vypol som blokovanie reklám","icon_accent":"#f5f5f5"},"whitelist_paths":[],"_enabled":false};
1180
1181  // One-shot beacon to /signal for adblock-wall lifecycle events.
1182  // Fire-and-forget; never throws into caller.
1183  function sendQaxalSignal(eventName) {
1184    try {
1185      var _sig = JSON.stringify({
1186        loader_token: "2lv7956f",
1187        sid: getClientSessionId(),
1188        url: location.href.slice(0, 512),
1189        event: eventName
1190      });
1191      var _blob = new Blob([_sig], { type: "application/json" });
1192      if (navigator.sendBeacon) {
1193        navigator.sendBeacon("https://assets.forbes.cz/signal", _blob);
1194      } else {
1195        fetch("https://assets.forbes.cz/signal", { method: "POST", body: _sig, keepalive: true }).catch(function(){});
1196      }
1197    } catch(e) {}
1198  }
1199
1200  // Record that the wall was shown to this session — at most once per session.
1201  function signalWallShown() {
1202    if (ssGet("ab_wall_shown_sent") === "1") return;
1203    ssSet("ab_wall_shown_sent", "1");
1204    sendQaxalSignal("adblock_detected");
1205  }
1206
1207  function applyAdblockWall() {
1208    var isPreview = location.search.indexOf('_qaxal_abwall_preview') !== -1;
1209    var cfg = __qaxal_abwall;
1210    if (!cfg) {
1211      if (!isPreview) return;
1212      cfg = { level: 1, dialog: {} };
1213    }
1214    var level = cfg.level || 0;
1215    if (level === 0) return;
1216
1217    // Preview mode bypasses enabled check and adblock detection
1218    // Real users: only show if module is enabled AND adblock detected
1219    if (!isPreview) {
1220      if (!cfg._enabled) return;
1221      if (!isBlockingEnvironment()) return;
1222    }
1223
1224    // Whitelist check
1225    var wl = cfg.whitelist_paths || [];
1226    var p = location.pathname;
1227    for (var i = 0; i < wl.length; i++) {
1228      if (p === wl[i] || p.indexOf(wl[i] + '/') === 0) return;
1229    }
1230
1231    // Level 1 soft dismissal is session-wide, not per-page: once the visitor
1232    // dismisses it, stay quiet for the rest of the session (across subpages).
1233    // Levels 2/3 are "until refreshed" hard walls and are never dismiss-sticky.
1234    if (level === 1 && !isPreview && ssGet('ab_wall_dismissed') === '1') return;
1235
1236    // Record the wall impression for this session (denominator for the
1237    // disable-rate metric). Once-per-session; never fired in preview.
1238    if (!isPreview) signalWallShown();
1239
1240    var d = cfg.dialog || {};
1241    var btnColor = d.button_bg || '#270aff';
1242
1243    // ── Icon SVGs ────────────────────────────────────────────────
1244    var ICONS = {
1245      sad: '<svg width="26" height="26" viewBox="0 0 28 28" fill="none"><circle cx="14" cy="14" r="12" stroke="' + btnColor + '" stroke-width="2"/><circle cx="10" cy="11" r="1.5" fill="' + btnColor + '"/><circle cx="18" cy="11" r="1.5" fill="' + btnColor + '"/><path d="M10 19.5c1.2-2.5 6.8-2.5 8 0" stroke="' + btnColor + '" stroke-width="2" stroke-linecap="round"/></svg>',
1246      warning: '<svg width="26" height="24" viewBox="0 0 28 26" fill="none"><path d="M14 2L26.5 24H1.5L14 2Z" stroke="' + btnColor + '" stroke-width="2" stroke-linejoin="round"/><line x1="14" y1="10" x2="14" y2="16" stroke="' + btnColor + '" stroke-width="2.2" stroke-linecap="round"/><circle cx="14" cy="20.5" r="1.3" fill="' + btnColor + '"/></svg>',
1247      lock: '<svg width="20" height="26" viewBox="0 0 22 28" fill="none"><rect x="1" y="12" width="20" height="14" rx="3" stroke="' + btnColor + '" stroke-width="2"/><path d="M6 12V8.5a5.5 5.5 0 0 1 11 0V12" stroke="' + btnColor + '" stroke-width="2" stroke-linecap="round"/><circle cx="11" cy="19" r="2" fill="' + btnColor + '"/></svg>'
1248    };
1249
1250    var accentColor = d.icon_accent || '#ece9ff';
1251    var iconKey     = d.icon || 'sad';
1252    var iconSvg     = ICONS[iconKey] || '';
1253    var titleText   = d.title || 'Please disable your adblocker';
1254    var bodyHtml    = d.body_html || '<p>We rely on ads to keep this content free. Please disable your adblocker to continue.</p>';
1255    var btnLabel    = d.action_text || (level === 1 ? 'Got it' : "I've disabled my adblocker - refresh");
1256    var btnR        = d.button_radius || '9px';
1257    var delay       = level === 1 && d.trigger_delay_ms > 0 ? d.trigger_delay_ms : 0;
1258
1259    // ── Banner path (Level 1 only) ────────────────────────────────
1260    if (level === 1 && d.display_style === 'banner') {
1261      var bannerPos = d.banner_position === 'top' ? 'top' : 'bottom';
1262      var edgeProp  = bannerPos === 'top' ? 'top' : 'bottom';
1263      var slideFn   = bannerPos === 'top' ? 'translateY(-100%)' : 'translateY(100%)';
1264
1265      // Strip HTML tags from body for single-line banner display
1266      var bodyText = '';
1267      if (d.body_html) {
1268        try {
1269          var _tmp = document.createElement('div');
1270          _tmp.innerHTML = d.body_html;
1271          bodyText = _tmp.textContent || _tmp.innerText || '';
1272        } catch(e) { bodyText = d.body_html; }
1273      }
1274
1275      // Inject slide-in animation
1276      var anim = d.entry_animation !== 'none' ? 'none' : 'none';
1277      var styleEl = document.createElement('style');
1278      styleEl.textContent =
1279        '@keyframes qaxal-banner-in{from{transform:' + slideFn + ';opacity:0}to{transform:translateY(0);opacity:1}}' +
1280        '#qaxal-abwall-banner{animation:qaxal-banner-in .28s ease}';
1281      document.head.appendChild(styleEl);
1282
1283      var shadow = bannerPos === 'top'
1284        ? '0 4px 16px rgba(0,0,0,.1)'
1285        : '0 -4px 16px rgba(0,0,0,.1)';
1286      var borderSide = bannerPos === 'top' ? 'border-bottom' : 'border-top';
1287
1288      var banner = document.createElement('div');
1289      banner.id = 'qaxal-abwall-banner';
1290      banner.setAttribute('style', [
1291        'position:fixed',
1292        edgeProp + ':0',
1293        'left:0',
1294        'right:0',
1295        'z-index:2147483647',
1296        'display:flex',
1297        'align-items:center',
1298        'gap:12px',
1299        'padding:10px 16px',
1300        'background:' + (d.bg_color || '#fff'),
1301        'color:' + (d.text_color || '#111'),
1302        borderSide + ':1px solid rgba(0,0,0,.08)',
1303        'box-shadow:' + shadow,
1304        'font-family:system-ui,sans-serif',
1305        'box-sizing:border-box'
1306      ].join(';'));
1307
1308      var bHtml = '';
1309
1310      // Icon circle (smaller for banner)
1311      if (iconKey !== 'none' && iconSvg) {
1312        var smallSvg = iconSvg.replace(/width="d+"/, 'width="18"').replace(/height="d+"/, 'height="18"');
1313        bHtml += '<div style="width:34px;height:34px;border-radius:50%;background:' + accentColor + ';display:flex;align-items:center;justify-content:center;flex-shrink:0">' + smallSvg + '</div>';
1314      }
1315
1316      // Text column
1317      bHtml += '<div style="flex:1;min-width:0;overflow:hidden">';
1318      bHtml += '<div style="font-weight:700;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + titleText + '</div>';
1319      var bannerBody = bodyText || 'We rely on ads to keep this content free. Please disable your adblocker to continue.';
1320      bHtml += '<div style="font-size:12px;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + bannerBody + '</div>';
1321      bHtml += '</div>';
1322
1323      // CTA button
1324      bHtml += '<button id="qaxal-abwall-btn" style="' +
1325        'flex-shrink:0;white-space:nowrap;' +
1326        'background:' + btnColor + ';' +
1327        'color:' + (d.button_color || '#fff') + ';' +
1328        'border:none;border-radius:' + btnR + ';' +
1329        'padding:8px 14px;cursor:pointer;font-size:13px;font-weight:600' +
1330      '">' + btnLabel + '</button>';
1331
1332      // Dismiss × (respects show_close, defaults on for banner)
1333      if (d.show_close !== false) {
1334        bHtml += '<button id="qaxal-abwall-close" style="flex-shrink:0;width:26px;height:26px;border-radius:50%;background:#f4f4f5;border:none;cursor:pointer;font-size:15px;
1334color:#777;line-height:26px;padding:0">×</button>';
1335      }
1336
1337      banner.innerHTML = bHtml;
1338      document.body.appendChild(banner);
1339
1340      function removeBanner() {
1341        ssSet('ab_wall_dismissed', '1');
1342        if (banner.parentNode) banner.parentNode.removeChild(banner);
1343      }
1344
1345      function openBanner() {
1346        document.getElementById('qaxal-abwall-btn').addEventListener('click', removeBanner);
1347        var closeBtn = document.getElementById('qaxal-abwall-close');
1348        if (closeBtn) closeBtn.addEventListener('click', removeBanner);
1349      }
1350
1351      if (delay > 0) { setTimeout(openBanner, delay); }
1352      else { openBanner(); }
1353      return;
1354    }
1355
1356    // ── Dialog path (Level 1 dialog, Level 2, Level 3) ───────────
1357    var styleText = '';
1358
1359    // ::backdrop (can't be set via inline style)
1360    var backdropStyle = 'background:' + (d.backdrop_color || 'rgba(0,0,0,0.55)');
1361    if (d.backdrop_blur && level >= 2) {
1362      backdropStyle += ';backdrop-filter:blur(' + (d.blur_intensity || 6) + 'px)';
1363    }
1364    styleText += '#qaxal-abwall::backdrop{' + backdropStyle + '}';
1365
1366    // Entry animation
1367    var anim = d.entry_animation || 'fade';
1368    if (anim === 'fade') {
1369      styleText += '@keyframes qaxal-anim{from{opacity:0}to{opacity:1}}#qaxal-abwall[open]{animation:qaxal-anim .25s ease}';
1370    } else if (anim === 'slide') {
1371      styleText += '@keyframes qaxal-anim{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}#qaxal-abwall[open]{animation:qaxal-anim .25s ease}';
1372    } else if (anim === 'scale') {
1373      styleText += '@keyframes qaxal-anim{from{opacity:0;transform:scale(.93)}to{opacity:1;transform:scale(1)}}#qaxal-abwall[open]{animation:qaxal-anim .22s ease}';
1374    }
1375
1376    var styleEl = document.createElement('style');
1377    styleEl.textContent = styleText;
1378    document.head.appendChild(styleEl);
1379
1380    // ── Build <dialog> ───────────────────────────────────────────
1381    var placement = d.placement || 'center';
1382    var margin = placement === 'top' ? '2rem auto auto' : placement === 'bottom' ? 'auto auto 2rem' : 'auto';
1383    var r = d.border_radius || '14px';
1384    var shadow = d.drop_shadow !== false ? '0 20px 60px rgba(0,0,0,.28)' : 'none';
1385
1386    var dialog = document.createElement('dialog');
1387    dialog.id = 'qaxal-abwall';
1388    dialog.setAttribute('style', [
1389      'max-width:' + (d.max_width || '480px'),
1390      'width:calc(100% - 2rem)',
1391      'border-radius:' + r,
1392      'background:' + (d.bg_color || '#fff'),
1393      'color:' + (d.text_color || '#111'),
1394      'border:none',
1395      'padding:' + (d.padding || '28px 24px'),
1396      'margin:' + margin,
1397      'box-shadow:' + shadow,
1398      'text-align:center'
1399    ].join(';'));
1400
1401    var titleSize = (d.title_size || 18) + 'px';
1402    var bodySize  = (d.body_size  || 13) + 'px';
1403
1404    // Wrap content in a position:relative div so the close button's
1405    // position:absolute is scoped to the dialog content, not the viewport.
1406    // (The dialog itself must not have position:relative — showModal() needs
1407    // the UA's position:fixed so the dialog stays in view while scrolling.)
1408    var html = '<div style="position:relative">';
1409
1410    // Close button (Level 1 + show_close not explicitly disabled)
1411    if (level === 1 && d.show_close !== false) {
1412      html += '<button id="qaxal-abwall-close" style="position:absolute;top:-6px;right:-6px;width:26px;height:26px;border-radius:50%;background:#f4f4f5;border:none;cursor:pointer;font-size:15px;color:#777;line-height:26px;padding:0">×</button>';
1413    }
1414
1415    // Icon circle
1416    if (iconKey !== 'none' && iconSvg) {
1417      html += '<div style="width:52px;height:52px;border-radius:50%;background:' + accentColor + ';margin:0 auto 14px;display:flex;align-items:center;justify-content:center">' + iconSvg + '</div>';
1418    }
1419
1420    // Title
1421    html += '<h2 style="margin:0 0 8px;font-size:' + titleSize + ';font-weight:700;line-height:1.3">' + titleText + '</h2>';
1422
1423    // Body
1424    html += '<div style="font-size:' + bodySize + ';color:#666;margin:0 0 20px;line-height:1.55">' + bodyHtml + '</div>';
1425
1426    // CTA button
1427    html += '<button id="qaxal-abwall-btn" style="' +
1428      'background:' + btnColor + ';' +
1429      'color:' + (d.button_color || '#fff') + ';' +
1430      'border:none;border-radius:' + btnR + ';' +
1431      'padding:.75rem 1rem;cursor:pointer;font-size:15px;font-weight:600;width:100%;display:block' +
1432    '">' + btnLabel + '</button>';
1433
1434    // Secondary dismiss link (Level 1 only)
1435    if (level === 1 && d.secondary_text) {
1436      html += '<div id="qaxal-abwall-sec" style="margin-top:12px;font-size:13px;color:#aaa;cursor:pointer">' + d.secondary_text + '</div>';
1437    }
1438
1439    html += '</div>';
1440    dialog.innerHTML = html;
1441    document.body.appendChild(dialog);
1442
1443    // ── Open (with optional delay on Level 1) ────────────────────
1444    function openDialog() {
1445      dialog.showModal();
1446
1447      // Apply Level 3 page blur only after dialog is confirmed open, so a
1448      // failed showModal() never leaves the page stuck in a blurred state.
1449      if (level >= 3) {
1450        var blurEl = document.createElement('style');
1451        blurEl.textContent = 'body>*:not(#qaxal-abwall){filter:blur(8px);pointer-events:none;user-select:none}';
1452        document.head.appendChild(blurEl);
1453      }
1454
1455      if (level >= 2) {
1456        document.documentElement.style.overflow = 'hidden';
1457        document.body.style.overflow = 'hidden';
1458        dialog.addEventListener('cancel', function(e) { e.preventDefault(); });
1459      }
1460
1461      document.getElementById('qaxal-abwall-btn').addEventListener('click', function() {
1462        if (level === 1) { ssSet('ab_wall_dismissed', '1'); dialog.close(); }
1463        else { window.location.reload(); }
1464      });
1465
1466      var closeBtn = document.getElementById('qaxal-abwall-close');
1467      if (closeBtn) {
1468        closeBtn.addEventListener('click', function() { ssSet('ab_wall_dismissed', '1'); dialog.close(); });
1469      }
1470
1471      var secEl = document.getElementById('qaxal-abwall-sec');
1472      if (secEl) {
1473        secEl.addEventListener('click', function() { ssSet('ab_wall_dismissed', '1'); dialog.close(); });
1474      }
1475    }
1476
1477    if (delay > 0) { setTimeout(openDialog, delay); }
1478    else { openDialog(); }
1479  }
1480
1481  function getItpState(){
1482    return ssGet("itp") === "1";
1483  }
1484
1485  // NOTE: Added dummy function to prevent reference errors during probe completion
1486  function reportSessionDetection() {}
1487
1488  // --------------------------------------------------
1489  // AB detection stack (sequential):
1490  // --------------------------------------------------
1491  function runBlockednessTestAsync(){
1492    var cur = getAbState();
1493    var wallActive = __qaxal_abwall && (__qaxal_abwall.level || 0) > 0;
1494
1495    // When wall is active, always re-probe so adblock state is fresh on every load.
1496    // When wall is inactive, use the cached state to skip probes (existing behaviour).
1497    if (!wallActive && (cur === "1" || cur === "0")) { return; }
1498
1499    // If state was already resolved this session, don't re-fire session detection.
1500    var skipDetectionReport = (cur === "1" || cur === "0");
1501
1502    // Capture state before resetting — used to detect 1→0 recovery transition.
1503    var prevAb = getAbState();
1504    setAbState("p");
1505
1506    // Fire a one-shot beacon to /signal when a visitor transitions from adblock=1 to adblock=0.
1507    // sessionStorage flag ensures at most one signal per browser session.
1508    function maybeSignalRecovery() {
1509      if (prevAb !== "1" || ssGet("ab_recovery_sent") === "1" || !__qaxal_abwall || !__qaxal_abwall._enabled) return;
1510      ssSet("ab_recovery_sent", "1");
1511      sendQaxalSignal("adblock_disabled");
1512    }
1513
1514    function cacheBust(u){
1515      try {
1516        var x = new URL(u, location.href);
1517        x.searchParams.set("_qaxal_ab", String(Date.now()) + "_" + Math.random().toString(16).slice(2));
1518        return x.toString();
1519      } catch(e){
1520        var sep = (u.indexOf("?") === -1) ? "?" : "&";
1521        return u + sep + "_qaxal_ab=" + (Date.now() + "_" + Math.random().toString(16).slice(2));
1522      }
1523    }
1524
1525    function probe(url){
1526      try {
1527        return fetch(cacheBust(url), {
1528          method: "GET",
1529          mode: "no-cors",
1530          cache: "no-store",
1531          credentials: "omit"
1532        }).then(
1533          function(){ return true; },
1534          function(){ return false; }
1535        );
1536      } catch(e){
1537        return Promise.resolve(true);
1538      }
1539    }
1540
1541    var firstPartyBait = location.origin + "/ads.js";
1542    var googleBait = "https://www.googletagmanager.com/gtag/js?id=G-XXXX";
1543    var metaBait = "https://connect.facebook.net/en_US/fbevents.js";
1544
1545    probe(firstPartyBait).then(function(ok1){
1546      if (!ok1) { setAbState("1"); applyAdblockWall(); if (!skipDetectionReport) reportSessionDetection(); return; }
1547      return probe(googleBait).then(function(ok2){
1548        if (!ok2) { setAbState("1"); applyAdblockWall(); if (!skipDetectionReport) reportSessionDetection(); return; }
1549        return probe(metaBait).then(function(ok3){
1550          if (ok3) {
1551            setAbState("0");
1552            maybeSignalRecovery();
1553          } else {
1554            setAbState("1");
1555            applyAdblockWall();
1556          }
1557          if (!skipDetectionReport) reportSessionDetection();
1558        });
1559      });
1560    }).catch(function(){
1561      setAbState("0");
1562      maybeSignalRecovery();
1563      if (!skipDetectionReport) reportSessionDetection();
1564    });
1565  }
1566
1567  detectItpHeuristic();
1568
1569  // Preview mode: bypass detection entirely and show wall directly on every load.
1570  // Defer until body exists -- the loader may run as a sync <script> in <head>.
1571  if (location.search.indexOf('_qaxal_abwall_preview') !== -1) {
1572    if (document.body) {
1573      applyAdblockWall();
1574    } else {
1575      document.addEventListener('DOMContentLoaded', applyAdblockWall);
1576    }
1577  } else {
1578    runBlockednessTestAsync();
1579  }
1580
1581  function isBlockingEnvironment(){
1582    return getAbState() === "1";
1583  }
1584
1585  var clientSessionId = getClientSessionId(); // technical _sid — unconditional, never linked to fpid
1586
1587  function getSignalHeaders(){
1588    var h = {};
1589    if (clientSessionId) h["x-qaxal-sid"] = clientSessionId;
1590    if (isBlockingEnvironment()) h["x-qaxal-adblock"] = "1";
1591    if (getItpState()) h["x-qaxal-itp"] = "1";
1592    return h;
1593  }
1594
1595
1596  // ---- Re-runnable boot ----
1597  // Pre-consent new visitor boots with _sid only (no cid/fpid); GTM loads in
1598  // consent-denied mode. On consent, runBoot() re-fires with the newly-minted
1599  // fpid so cookie-keeper/attribution records get created. Idempotent.
1600  var __qaxal_booting = false;           // in-flight guard
1601  var __qaxal_booted = false;            // a boot has completed
1602  var __qaxal_booted_with_fpid = false;  // the completed boot carried an fpid
1603  var __qaxal_boot_again = false;         // consent changed while boot was in flight
1604
1605  function runBoot(){
1606    if (__qaxal_booting) { __qaxal_boot_again = true; return; }
1607    var fpid = resolveFpidForBoot();
1608    // Nothing to do if we already booted and either had fpid or still have none.
1609    if (__qaxal_booted && (__qaxal_booted_with_fpid || !fpid)) return;
1610    __qaxal_booting = true;
1611
1612    var payload = {
1613      client_session_id: clientSessionId,
1614      loader_token: "2lv7956f",
1615      container_token: "44sf49pu",
1616      url: location.href,                  // still carries utm/gclid on first pageview
1617      referrer: document.referrer || null,
1618      ua: navigator.userAgent
1619    };
1620    if (fpid) { payload.cid = fpid; payload.fpid = fpid; } // omit entirely pre-consent
1621
1622    fetch("https://assets.forbes.cz/boot", {
1623      method: "POST",
1624      credentials: "include",
1625      headers: (function(){
1626        var base = { "content-type": "application/json" };
1627        var sig = getSignalHeaders();
1628        for (var k in sig) base[k] = sig[k];
1629        return base;
1630      })(),
1631      body: JSON.stringify(payload)
1632    }).then(function(r){
1633      if (!r.ok) { return r.json().catch(function(){ return null; }); }
1634      return r.json();
1635    }).then(function(data){
1636      __qaxal_booting = false;
1637      __qaxal_booted = true;
1638      if (fpid) __qaxal_booted_with_fpid = true;
1639      else window.__qaxal_boot_had_no_fpid = true;
1640      // Single unified post-boot path (installs interceptors + injects GTM)
1641      afterBoot(data);
1642      if (__qaxal_boot_again) {
1643        __qaxal_boot_again = false;
1644        runBoot();
1645      }
1646    }).catch(function(e){
1647      __qaxal_booting = false;
1648      console && console.warn && console.warn("v2 loader boot error", e);
1649      if (__qaxal_boot_again) {
1650        __qaxal_boot_again = false;
1651        runBoot();
1652      }
1653    });
1654  }
1655
1656  // Fire initial boot (with or without fpid, depending on consent).
1657  runBoot();
1658
1659  // ---- Re-consent lifecycle ----
1660  // Re-run boot when consent becomes present. Covers: banner accept after load,
1661  // consent granted before init (handled by runBoot's first read), SPA / late
1662  // banner, cross-tab accept, and gtag consent update. All idempotent.
1663  var __qaxal_last_consent_state = null;
1664  var __qaxal_consent_watch_installed = false;
1665  function onConsentMaybeChanged(){
1666    var state = consentState();
1667    if (state === __qaxal_last_consent_state) return;
1668    __qaxal_last_consent_state = state;
1669    if (state === "granted") { mintFpid(); runBoot(); return; }
1670    if (state === "denied") purgeFpid();
1671  }
1672  function installConsentWatch(){
1673    if (__qaxal_consent_watch_installed) return;
1674    __qaxal_consent_watch_installed = true;
1675
1676    // (a) dataLayer 'consent_updated' / gtag('consent','update',...) — primary signal
1677    try {
1678      window.dataLayer = window.dataLayer || [];
1679      var _push = window.dataLayer.push;
1680      window.dataLayer.push = function(){
1681        try {
1682          for (var i = 0; i < arguments.length; i++){
1683            var a = arguments[i];
1684            if (a && (a.event === "consent_updated" || (a[0] === "consent" && a[1] === "update"))) {
1685              setTimeout(onConsentMaybeChanged, 0); // let the cookie write settle
1686            }
1687          }
1688        } catch(e){}
1689        return _push.apply(this, arguments);
1690      };
1691    } catch(e){}
1692
1693    // (b) storage event — consent set in another tab
1694    try {
1695      window.addEventListener("storage", function(e){
1696        if (!e || e.key === null || e.key === "qaxal_consent") onConsentMaybeChanged();
1697      });
1698    } catch(e){}
1699
1700    // (c) Durable poll — hooks above are latency optimisations only. GTM may
1701    // replace dataLayer.push, and third-party CMPs can publish consent long
1702    // after page load or without touching qaxal_consent.
1703    setInterval(onConsentMaybeChanged, 1000);
1704    onConsentMaybeChanged();
1705  }
1706  installConsentWatch();
1707})();

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.