PageSourceSearch

https://denikalarm.cz/wp-content/themes/alarm/assets/js/seznam.js?ver=1780597173

js denikalarm.cz collected 2026-10-02 15:08:25 UTC 24,038 bytes, 713 lines download raw bytes

1/**
2 * Seznam Ads Client-Side Manager
3 *
4 * Handles ALL Seznam ad functionality client-side:
5 * 1. Detecting Seznam visitors via UTM params, referrer, or cookie
6 * 2. Managing the isFromSeznam cookie (set, prolong, clear)
7 * 3. Dynamically loading the Seznam SSP script
8 * 4. Creating and injecting ad containers based on page type and device
9 * 5. Requesting ads from Seznam SSP
10 * 6. Handling the mobile pop-up ad (zone 397782) on single posts
11 * 7. MutationObserver for dynamically loaded posts (infinite scroll)
12 */
13(function () {
14    'use strict';
15
16    // ========== Configuration ==========
17
18    const CONFIG = {
19        SITE_DOMAIN: 'denikalarm.cz',
20        SEZNAM_DOMAIN: 'www.seznam.cz',
21        // Seznam now identifies all distribution channels with a single medium
22        // (seznam_distribuce); individual channels are distinguished by campaign.
23        VALID_MEDIUMS: ['seznam_distribuce'],
24        VALID_CAMPAIGNS: [
25            'hp_feed',          // Newsfeed
26            'hp_box',           // HP Boxy
27            'novinky',          // Novinky / Denní tisk
28            'sbrowser',         // Seznam Prohlížeč
29            'trending_topics',  // Trending Topics
30            'asistent',         // Asistent
31            'aifunkce',         // AI funkce
32            'lide'              // Lidé
33        ],
34        COOKIE_DURATION_MS: 30 * 60 * 1000, // 30 minutes
35        SCRIPTS: {
36            ssp: 'https://ssp.seznam.cz/static/js/ssp.js'
37        },
38        TEST_MODE: false, // Enable for fill rate validation
39        DEBUG: false // Enable console logging
40    };
41
42    // Ad zone definitions
43    const AD_ZONES = {
44        // Desktop zones
45        desktopLeaderboard: { zoneId: 150335, width: 970, height: 210 },
46        desktopInContent: { zoneId: 215091, width: 480, height: 300 },
47        desktopAfterContent: { zoneId: 150350, width: 111, height: 111 },
48        desktopFooterRectangle: { zoneId: 150355, width: 970, height: 310 },
49
50        // Mobile zones
51        mobileFirstPara: { zoneId: 150360, width: 480, height: 480 },
52        mobileRepeating: { zoneId: 242045, width: 480, height: 480 },
53        mobileAfterContent: { zoneId: 150365, width: 111, height: 111 },
54        mobileFooter: { zoneId: 150370, width: 480, height: 480 },
55
56        // Mobile pop-up reload (replaces former "Pop up mobil" zone 241544)
57        mobilePopupReload: { zoneId: 397782, width: 500, height: 200 }
58    };
59
60    // State
61    const state = {
62        isSeznamVisitor: false,
63        scriptsLoaded: false,
64        pendingAds: [],
65        initialized: false,
66        pageType: null, // 'single', 'archive', 'home', 'search', 'category'
67        screenType: null // 'mobile', 'desktop'
68    };
69
70    // ========== Logging ==========
71
72    function log(msg, data = null) {
73        if (!CONFIG.DEBUG) return;
74        const prefix = '[SeznamAds]';
75        if (data !== null) {
76            console.log(`${prefix} ${msg}`, data);
77        } else {
78            console.log(`${prefix} ${msg}`);
79        }
80    }
81
82    // ========== Cookie Utilities ==========
83
84    function getCookie(name) {
85        const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
86        return match ? match[2] : null;
87    }
88
89    function setCookie(name, value, durationMs) {
90        const expires = new Date(Date.now() + durationMs).toUTCString();
91        document.cookie = `${name}=${value}; path=/; expires=${expires}; SameSite=Lax`;
92        log(`Cookie set: ${name}=${value}`);
93    }
94
95    function clearCookie(name) {
96        document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
97        log(`Cookie cleared: ${name}`);
98    }
99
100    // ========== Detection Logic ==========
101
102    function detectSeznamVisitor() {
103        const params = new URLSearchParams(window.location.search);
104        const referer = document.referrer;
105        const hasSeznamCookie = getCookie('isFromSeznam') === 'true';
106
107        // Check if coming from Seznam via UTM params
108        const utmSource = params.get('utm_source');
109        const utmMedium = params.get('utm_medium');
110        const utmCampaign = params.get('utm_campaign');
111        const isFromSeznamUtm = utmSource === CONFIG.SEZNAM_DOMAIN &&
112            CONFIG.VALID_MEDIUMS.includes(utmMedium) &&
113            CONFIG.VALID_CAMPAIGNS.includes(utmCampaign);
114
115        // Check if coming from Seznam via referrer
116        const isFromSeznamReferer = referer.includes(CONFIG.SEZNAM_DOMAIN);
117
118        // Check if internal navigation
119        const isInternalNav = referer.includes(CONFIG.SITE_DOMAIN);
120
121        log('Detection:', { utmSource, utmMedium, utmCampaign, isFromSeznamUtm, isFromSeznamReferer, isInternalNav, hasSez
121namCookie });
122
123        let isSeznamVisitor = false;
124
125        if (isFromSeznamUtm || isFromSeznamReferer) {
126            // Direct arrival from Seznam
127            isSeznamVisitor = true;
128            setCookie('isFromSeznam', 'true', CONFIG.COOKIE_DURATION_MS);
129        } else if (hasSeznamCookie) {
130            if (isInternalNav) {
131                // Internal navigation - prolong cookie
132                isSeznamVisitor = true;
133                setCookie('isFromSeznam', 'true', CONFIG.COOKIE_DURATION_MS);
134            } else {
135                // External referrer or direct navigation - clear cookie
136                isSeznamVisitor = false;
137                clearCookie('isFromSeznam');
138            }
139        }
140
141        log(`Visitor status: ${isSeznamVisitor ? 'Seznam' : 'Non-Seznam'}`);
142        return isSeznamVisitor;
143    }
144
145    // ========== Page Type Detection ==========
146
147    function detectPageType() {
148        const body = document.body;
149
150        if (body.classList.contains('single') || body.classList.contains('single-post')) {
151            return 'single';
152        }
153        if (body.classList.contains('home')) {
154            return 'home';
155        }
156        if (body.classList.contains('search')) {
157            return 'search';
158        }
159        if (body.classList.contains('category')) {
160            return 'category';
161        }
162        if (body.classList.contains('archive') || body.classList.contains('tag')) {
163            return 'archive';
164        }
165
166        return 'other';
167    }
168
169    function detectScreenType() {
170        return window.innerWidth <= 768 ? 'mobile' : 'desktop';
171    }
172
173    // ========== Script Loading ==========
174
175    function loadScript(src) {
176        return new Promise((resolve, reject) => {
177            log(`Loading script: ${src}`);
178            const script = document.createElement('script');
179            script.src = src;
180            script.async = true;
181
182            script.onload = () => {
183                log(`Script loaded: ${src}`);
184                resolve();
185            };
186
187            script.onerror = (err) => {
188                log(`Script failed: ${src}`, err);
189                reject(err);
190            };
191
192            document.head.appendChild(script);
193        });
194    }
195
196    function waitForSSSP(timeout = 5000) {
197        return new Promise((resolve, reject) => {
198            const start = Date.now();
199
200            function check() {
201                if (window.sssp && typeof window.sssp.getAds === 'function') {
202                    log(`SSSP ready after ${Date.now() - start}ms`);
203                    resolve(window.sssp);
204                    return;
205                }
206
207                if (Date.now() - start > timeout) {
208                    reject(new Error('SSSP timeout'));
209                    return;
210                }
211
212                setTimeout(check, 50);
213            }
214
215            check();
216        });
217    }
218
219    async function loadSeznamScripts() {
220        if (!state.isSeznamVisitor) {
221            log('Skipping script load (not Seznam visitor)');
222            return false;
223        }
224
225        try {
226            // Load SSP script
227            await loadScript(CONFIG.SCRIPTS.ssp);
228
229            // Wait for SSSP initialization
230            await waitForSSSP();
231            state.scriptsLoaded = true;
232
233            // Configure test mode if enabled
234            if (CONFIG.TEST_MODE && window.sssp && typeof window.sssp.config === 'function') {
235                log('Enabling TEST mode');
236                window.sssp.config({ test: true });
237            }
238
239            // Process pending ads
240            if (state.pendingAds.length > 0) {
241                log(`Processing ${state.pendingAds.length} pending ads`);
242                flushAds(state.pendingAds);
243                state.pendingAds = [];
244            }
245
246            return true;
247        } catch (error) {
248            console.error('[SeznamAds] Failed to load scripts:', error);
249            return false;
250        }
251    }
252
253    // ========== Ad Container Creation ==========
254
255    function createAdContainer(id, zone, screenType, extraClasses = '') {
256        const div = document.createElement('div');
257        div.id = id;
258        div.setAttribute('data-zone', zone.zoneId);
259        div.setAttribute('data-w', zone.width);
260        div.setAttribute('data-h', zone.height);
261        div.setAttribute('data-screen-type', screenType);
262
263        const classes = ['seznam-ad'];
264        classes.push(screenType === 'mobile' ? 'mobile-only-ad' : 'de
264sktop-only-ad');
265        if (extraClasses) {
266            classes.push(...extraClasses.split(' '));
267        }
268        div.className = classes.join(' ');
269
270        return div;
271    }
272
273    function injectAdContainer(id, zone, screenType, targetSelector, position = 'afterend', extraClasses = '') {
274        // Check if container already exists
275        if (document.getElementById(id)) {
276            log(`Container ${id} already exists`);
277            return false;
278        }
279
280        const target = document.querySelector(targetSelector);
281        if (!target) {
282            log(`Target not found: ${targetSelector}`);
283            return false;
284        }
285
286        const container = createAdContainer(id, zone, screenType, extraClasses);
287        target.insertAdjacentElement(position, container);
288        log(`Injected ${id} ${position} ${targetSelector}`);
289        return true;
290    }
291
292    // ========== Ad Injection by Page Type ==========
293
294    function injectSinglePostAds() {
295        const isDesktop = state.screenType === 'desktop';
296        const isMobile = state.screenType === 'mobile';
297
298        if (isDesktop) {
299            // Desktop: Top leaderboard (pre-rendered in PHP for CLS, just verify)
300            const prerenderedLeaderboard = document.getElementById('ssp-zone-150335-post');
301            if (!prerenderedLeaderboard) {
302                injectAdContainer(
303                    'ssp-zone-150335-post',
304                    AD_ZONES.desktopLeaderboard,
305                    'desktop',
306                    '.wp-site-blocks',
307                    'beforebegin',
308                    'seznam-leaderboard'
309                );
310            }
311
312            // Desktop: After first paragraph
313            injectAdContainer(
314                'ssp-zone-215091',
315                AD_ZONES.desktopInContent,
316                'desktop',
317                '.entry-content > p:nth-of-type(1):not(:empty)'
318            );
319
320            // Desktop: After content
321            injectAdContainer(
322                'ssp-zone-150350',
323                AD_ZONES.desktopAfterContent,
324                'desktop',
325                '.entry-content'
326            );
327
328            // Desktop: Rectangle before footer
329            injectAdContainer(
330                'ssp-zone-150355-post',
331                AD_ZONES.desktopFooterRectangle,
332                'desktop',
333                '.wp-site-blocks > footer',
334                'beforebegin',
335                'seznam-footer-ad'
336            );
337        }
338
339        if (isMobile) {
340            // Mobile: After first paragraph
341            injectAdContainer(
342                'ssp-zone-150360',
343                AD_ZONES.mobileFirstPara,
344                'mobile',
345                '.entry-content > p:nth-of-type(1):not(:empty)'
346            );
347
348            // Mobile: Repeating ads every 4th paragraph
349            const paragraphs = document.querySelectorAll('.entry-content > p');
350            let paraCounter = 0;
351            paragraphs.forEach((para) => {
352                if (!para.innerText || para.innerText.trim() === '') return;
353                paraCounter++;
354                if (paraCounter > 1 && paraCounter % 4 === 0) {
355                    const adId = `ssp-zone-mobile-post-${paraCounter}`;
356                    if (!document.getElementById(adId)) {
357                        const container = createAdContainer(adId, AD_ZONES.mobileRepeating, 'mobile');
358                        para.insertAdjacentElement('afterend', container);
359                        log(`Injected repeating ad after paragraph ${paraCounter}`);
360                    }
361                }
362            });
363
364            // Mobile: After content
365            injectAdContainer(
366                'ssp-zone-150365',
367                AD_ZONES.mobileAfterContent,
368                'mobile',
369                '.entry-content'
370            );
371
372            // Mobile: Before footer (po Čtěte dále)
373            injectAdContainer(
374                'ssp-zone-150370-po-ctete-dale',
375                AD_ZONES.mobileFooter,
376                'mobile',
377                '.single .wp-site-blocks > footer, .wp-site-blocks > footer',
378                'beforebegin'
379            );
380
381            // Mobile: Initialize pop-up reload
382            initMobilePopup();
383        }
384    }
385
386    function injectArchiveAds() {
387        const isDesktop = state.screenType === 'desktop';
388        const isMobile = state.screenType === 'mobile';
389
390        // Find first query item (works for home, archive, category, search, tag)
391        const queryItemSelector = [
392            '.home .wp-block-query',
393            '.archive #query > .query-item:first-child',
394            '.tag #query > .query-item:first-child',
395            '.category #query > .query-item:first-child',
396            '.search #query > .query-item:first-child',
397            '#query > .query-item:first-child'
398        ].join(', ');
399
400        if (isDesktop) {
401            // Desktop: Top leaderboard (pre-rendered in PHP for CLS, just verify)
402            const prerenderedLeaderboard = document.getElementById('ssp-zone-150335');
403            if (!prerenderedLeaderboard) {
404                injectAdContainer(
405                    'ssp-zone-150335',
406                    AD_ZONES.desktopLeaderboard,
407                    'desktop',
408                    '.wp-site-blocks',
409                    'beforebegin',
410                    'seznam-leaderboard'
411                );
412            }
413
414            // Desktop: After first article (wallpaper)
415            injectAdContainer(
416                'ssp-zone-215091-archive',
417                AD_ZONES.desktopInContent,
418                'desktop',
419                queryItemSelector
420            );
421
422            // Desktop: Rectangle before footer
423            injectAdContainer(
424                'ssp-zone-150355',
425                AD_ZONES.desktopFooterRectangle,
426                'desktop',
427                '.wp-site-blocks > footer',
428                'beforebegin',
429                'seznam-footer-ad'
430            );
431        }
432
433        if (isMobile) {
434            // Mobile: After first article
435            injectAdContainer(
436                'ssp-zone-150360-archive',
437                AD_ZONES.mobileFirstPara,
438                'mobile',
439                queryItemSelector
440            );
441
442            // Mobile: Footer
443            injectAdContainer(
444                'ssp-zone-150370',
445                AD_ZONES.mobileFooter,
446                'mobile',
447                '.wp-site-blocks > footer',
448                'beforebegin',
449                'seznam-footer-ad'
450            );
451
452            // Setup MutationObserver for infinite scroll
453            setupArchiveMutationObserver();
454        }
455    }
456
457    function injectAdsForPageType() {
458        if (!state.isSeznamVisitor) {
459            log('Skipping ad injection (not Seznam visitor)');
460            return;
461        }
462
463        log(`Injecting ads for page type: ${state.pageType}, screen: ${state.screenType}`);
464
465        if (state.pageType === 'single') {
466            injectSinglePostAds();
467        } else if (['home', 'archive', 'category', 'search'].includes(state.pageType)) {
468            injectArchiveAds();
469        }
470    }
471
472    // ========== Mobile Pop-up ==========
473
474    function initMobilePopup() {
475        if (state.screenType !== 'mobile') return;
476        if (state.pageType !== 'single') return;
477
478        if (!state.scriptsLoaded || !window.sssp || typeof window.sssp.getAds !== 'function') {
479            log('Pop-up skipped - SSSP not ready');
480            return;
481        }
482
483        const zone = AD_ZONES.mobilePopupReload;
484        const containerId = `ssp-zone-${zone.zoneId}`;
485
486        // Create the pop-up container if it doesn't exist yet
487        if (!document.getElementById(containerId)) {
488            const container = document.createElement('div');
489            container.id = containerId;
490            container.className = 'seznam-ad seznam-popup mobile-only-ad';
491            document.body.appendChild(container);
492        }
493
494        // Single request only - no reloading (reloadCount stays 0)
495        const adObj = {
496            zoneId: zone.zoneId,
497            id: containerId,
498            width: zone.width,
499            height: zone.height,
500            options: {
501                reloadCount: 0
502            }
503        };
504
505        log('Requesting mobile pop-up', adObj);
506        window.sssp.getAds([adObj]);
507    }
508
509    // ========== Archive MutationObserver ==========
510
511    let archiveAdCounter = 0;
512    let mutationDebounce = null;
513
514    function setupArchiveMutationObserver() {
515        const queryContainer = document.getElementById('query');
516        if (!queryContainer) {
517            log('Query container not found for MutationObserver');
518            return;
519        }
520
521        log('Setting up MutationObserver for infinite scroll');
522
523        const observer = new MutationObserver((mutations) => {
524            for (const mutation of mutations) {
525                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
526                    // Debounce to avoid multiple calls
527                    if (mutationDebounce) {
528                        clearTimeout(mutationDebounce);
529                    }
530
531                    mutationDebounce = setTimeout(() => {
532                        log('New posts detected via MutationObserver');
533                        injectAdsIntoNewPosts(mutation.addedNodes);
534                    }, 300);
535                }
536            }
537        });
538
539        observer.observe(queryContainer, {
540            childList: true,
541            subtree: false
542        });
543    }
544
545    function injectAdsIntoNewPosts(nodes) {
546        if (!state.isSeznamVisitor) return;
547
548        // Count valid query items added
549        let newItemCount = 0;
550        nodes.forEach(node => {
551            if (node.nodeType === 1 && node.classList && node.classList.contains('query-item')) {
552                newItemCount++;
553                archiveAdCounter++;
554
555                // Inject ad every 4th post
556                if (archiveAdCounter % 4 === 0) {
557                    const adId = `ssp-zone-mobile-archive-${archiveAdCounter}`;
558                    const container = createAdContainer(adId, AD_ZONES.mobileRepeating, 'mobile');
559                    node.insertAdjacentElement('afterend', container);
560                    log(`Injected archive ad after item ${archiveAdCounter}`);
561                }
562            }
563        });
564
565        // Scan for new ads and request them
566        if (newItemCount > 0) {
567            setTimeout(() => scanForAds(), 100);
568        }
569    }
570
571    // ========== Ad Request Management ==========
572
573    let adsBuffer = [];
574    let flushTimeout = null;
575
576    function flushAds(adsToRequest) {
577        if (!state.isSeznamVisitor || adsToRequest.length === 0) return;
578
579        log(`Flushing ${adsToRequest.length} ads`, adsToRequest);
580
581        if (state.scriptsLoaded && window.sssp && typeof window.sssp.getAds === 'function') {
582            window.sssp.getAds(adsToRequest);
583        } else {
584            log('Scripts not ready, queuing ads');
585            state.pendingAds.push(...adsToRequest);
586        }
587    }
588
589    function pushAd(ad) {
590        adsBuffer.push(ad);
591
592        if (flushTimeout) {
593            clearTimeout(flushTimeout);
594        }
595
596        // Debounce requests by 250ms
597        flushTimeout = setTimeout(() => {
598            const adsToProcess = [...adsBuffer];
599            adsBuffer = [];
600            flushAds(adsToProcess);
601        }, 250);
602    }
603
604    function loadSeznamAdById(id, zoneId = false) {
605        const ad = document.getElementById(id);
606        if (!ad) {
607            log(`Ad container not found: ${id}`);
608            return;
609        }
610
611        // Avoid double-loading
612        if (ad.getAttribute('data-ad-queued') === 'true') {
613            log(`Ad ${id} already queued`);
614            return;
615        }
616
617        // Check screen type match
618        const targetScreenType = ad.getAttribute('data-screen-type');
619        if (targetScreenType && targetScreenType !== state.screenType) {
620            log(`Skipping ad ${id} - wrong screen type`);
621            return;
622        }
623
624        const adObj = {
625            zoneId: zoneId || ad.getAttribute('data-zone') || ad.id.replace('ssp-zone-', '').split('-')[0],
626            id: ad.getAttribute('id'),
627            width: parseInt(ad.getAttribute('data-w'), 10),
628            height: parseInt(ad.getAttribute('data-h'), 10)
629        };
630
631        ad.setAttribute('data-ad-queued', 'true');
632        pushAd(adObj);
633    }
634
635    function scanForAds() {
636        if (!state.isSeznamVisitor) return;
637
638        const ads = document.querySelectorAll('.seznam-ad:not([data-ad-queued="true"])');
639        log(`Scanning: found ${ads.length} new ads`);
640
641        ads.forEach(ad => {
642            const id = ad.getAttribute('id');
643            const zoneId = ad.getAttribute('data-zone');
644            const targetScreenType = ad.getAttribute('data-screen-type');
645
646            // Skip ads for wrong screen type
647            if (targetScreenType && targetScreenType !== state.screenType) {
648                return;
649            }
650
651            loadSeznamAdById(id, zoneId);
652        });
653    }
654
655    // ========== Body Class Management ==========
656
657    function updateBodyClass() {
658        if (state.isSeznamVisitor) {
659            document.body.classList.remove('non-seznam-traffic');
660            document.body.classList.add('seznam-traffic');
661        } else {
662            document.body.classList.add('non-seznam-traffic');
663            document.body.classList.remove('seznam-traffic');
664        }
665    }
666
667    // ========== Initialization ==========
668
669    function init() {
670        if (state.initialized) return;
671        state.initialized = true;
672
673        // Detect visitor and page context
674        state.isSeznamVisitor = detectSeznamVisitor();
675        state.pageType = detectPageType();
676        state.screenType = detectScreenType();
677
678        log(`Init: visitor=${state.isSeznamVisitor}, page=${state.pageType}, screen=${state.screenType}`);
679
680        // Update body class
681        updateBodyClass();
682
683        // Expose public API
684        window.ad_manager = window.ad_manager || {};
685        window.ad_manager.loadSeznamAdById = loadSeznamAdById;
686        window.ad_manager.scanForAds = scanForAds;
687        window.ad_manager.loadAjaxPostsAds = scanForAds;
688        window.ad_manager.screenType = state.screenType;
689        window.ad_manager.isSeznamVisitor = state.isSeznamVisitor;
690
691        if (!state.isSeznamVisitor) {
692            log('Not Seznam visitor - ads will not load');
693            return;
694        }
695
696        // Load Seznam scripts
697        loadSeznamScripts().then(() => {
698            // Inject ad containers for page type
699            injectAdsForPageType();
700
701            // Scan for any ads (including pre-rendered ones)
702            setTimeout(() => scanForAds(), 100);
703        });
704    }
705
706    // Start initialization when DOM is ready
707    if (document.readyState === 'loading') {
708        document.addEventListener('DOMContentLoaded', init);
709    } else {
710        init();
711    }
712
713})();

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.