PageSourceSearch

https://hodnoceniher.cz/js/hh.js?v13

js hodnoceniher.cz collected 2026-10-02 19:09:38 UTC 18,349 bytes, 462 lines download raw bytes

1(function () {
2    'use strict';
3
4    var STORAGE_KEY = 'hh-theme';
5    var root = document.documentElement;
6    var systemDark = window.matchMedia('(prefers-color-scheme: dark)');
7
8    function storedTheme() {
9        try {
10            return localStorage.getItem(STORAGE_KEY);
11        } catch (e) {
12            return null;
13        }
14    }
15
16    function currentTheme() {
17        return root.dataset.theme || (systemDark.matches ? 'dark' : 'light');
18    }
19
20    function applyTheme(theme) {
21        var systemTheme = systemDark.matches ? 'dark' : 'light';
22        try {
23            // choice equal to the system setting means "follow the system" again
24            if (theme === systemTheme) {
25                localStorage.removeItem(STORAGE_KEY);
26            } else {
27                localStorage.setItem(STORAGE_KEY, theme);
28            }
29        } catch (e) {
30        }
31        if (theme === systemTheme) {
32            delete root.dataset.theme;
33        } else {
34            root.dataset.theme = theme;
35        }
36        syncThemeUi();
37        document.dispatchEvent(new CustomEvent('hh:themechange', {detail: {theme: currentTheme()}}));
38        track('theme_change', {theme: currentTheme()});
39    }
40
41    /**
42     * GA4 event (gtag is defined in the page head, it only queues when the GA script is blocked).
43     */
44    function track(name, params) {
45        if (typeof window.gtag === 'function') {
46            window.gtag('event', name, params || {});
47        }
48    }
49
50    function syncThemeUi() {
51        var theme = currentTheme();
52        document.querySelectorAll('[data-theme-toggle]').forEach(function (button) {
53            button.setAttribute('aria-pressed', theme === 'dark' ? 'true' : 'false');
54            button.setAttribute('title', button.dataset[theme === 'dark' ? 'labelLight' : 'labelDark'] || '');
55        });
56        var meta = document.querySelector('meta[name="theme-color"]');
57        if (meta) {
58            meta.setAttribute('content', theme === 'dark' ? '#0b0f17' : '#ffffff');
59        }
60    }
61
62    // <a data-track="event_name" data-track-game="Doom"> sends event_name {game: 'Doom'}
63    document.addEventListener('click', function (event) {
64        var element = event.target.closest('[data-track]');
65        if (!element) {
66            return;
67        }
68        // FAQ: count only opening of the question
69        if (element.tagName === 'SUMMARY' && element.parentElement && element.parentElement.open) {
70            return;
71        }
72        var params = {};
73        Object.keys(element.dataset).forEach(function (key) {
74            if (key.indexOf('track') === 0 && key.length > 5) {
75                var name = key.slice(5).replace(/[A-Z]/g, function (letter) { return '_' + letter.toLowerCase(); }).replace(/^_/, '');
76                params[name] = element.dataset[key];
77            }
78        });
79        if (element.href) {
80            params.link_url = element.href;
81        }
82        track(element.dataset.track, params);
83    }, true);
84
85    // search redirects directly to the found tag/game, so GA4 would not see the ?q= parameter
86    document.addEventListener('submit', function (event) {
87        var form = event.target.closest('form.search');
88        var input = form ? form.querySelector('input[name="q"]') : null;
89        if (input && input.value.trim() !== '') {
90            track('search', {search_term: input.value.trim()});
91        }
92    }, true);
93
94    document.addEventListener('click', function (event) {
95        var themeButton = event.target.closest('[data-theme-toggle]');
96        if (themeButton) {
97            applyTheme(currentTheme() === 'dark' ? 'light' : 'dark');
98            return;
99        }
100
101        var navButton = event.target.closest('[data-nav-toggle]');
102        if (navButton) {
103            var header = navButton.closest('.site-header');
104            var open = header.classList.toggle('is-open');
105            navButton.setAttribute('aria-expanded', open ? 'true' : 'false');
106        }
107    });
108
109    systemDark.addEventListener('change', function () {
110        if (!storedTheme()) {
111            syncThemeUi();
112            document.dispatchEvent(new CustomEvent('hh:themechange', {detail: {theme: currentTheme(
112)}}));
113        }
114    });
115
116    syncThemeUi();
117
118    // TOPlist counter with referrer (instead of blocking document.write), only with consent to analytics
119    function loadToplist() {
120        document.querySelectorAll('img[data-toplist]').forEach(function (img) {
121            if (img.dataset.loaded) {
122                return;
123            }
124            img.dataset.loaded = '1';
125            img.src = 'https://toplist.cz/count.asp?id=97824&logo=mc&http=' + encodeURIComponent(document.referrer)
126                + '&t=' + encodeURIComponent(document.title) + '&wi=' + screen.width + '&he=' + screen.height + '&cd=' + screen.colorDepth;
127        });
128    }
129
130    /* ---------- Cookie consent (Google Consent Mode v2, defaults are set in the page head) ---------- */
131
132    var CONSENT_KEY = 'hh-consent';
133    var consentBar = document.querySelector('[data-consent]');
134
135    function applyConsent(consent) {
136        var ads = consent.ads ? 'granted' : 'denied';
137        if (typeof window.gtag === 'function') {
138            window.gtag('consent', 'update', {
139                analytics_storage: consent.analytics ? 'granted' : 'denied',
140                ad_storage: ads,
141                ad_user_data: ads,
142                ad_personalization: ads,
143                personalization_storage: ads
144            });
145            window.gtag('set', 'ads_data_redaction', !consent.ads);
146        }
147        if (consent.analytics) {
148            loadToplist();
149        }
150    }
151
152    function saveConsent(analytics, ads) {
153        var consent = {v: 1, analytics: !!analytics, ads: !!ads, ts: Date.now()};
154        try {
155            localStorage.setItem(CONSENT_KEY, JSON.stringify(consent));
156        } catch (e) {
157        }
158        window.hhConsent = consent;
159        applyConsent(consent);
160        track('consent_update', {analytics: consent.analytics ? 'granted' : 'denied', ads: consent.ads ? 'granted' : 'denied'});
161        hideConsentBar();
162    }
163
164    function showConsentBar(withSettings) {
165        if (!consentBar) {
166            return;
167        }
168        var current = window.hhConsent || {analytics: false, ads: false};
169        consentBar.querySelector('[data-consent-analytics]').checked = !!current.analytics;
170        consentBar.querySelector('[data-consent-ads]').checked = !!current.ads;
171        toggleConsentSettings(!!withSettings);
172        consentBar.hidden = false;
173        document.body.classList.add('has-consent-bar');
174    }
175
176    function hideConsentBar() {
177        if (consentBar) {
178            consentBar.hidden = true;
179            document.body.classList.remove('has-consent-bar');
180        }
181    }
182
183    function toggleConsentSettings(open) {
184        consentBar.querySelector('[data-consent-settings]').hidden = !open;
185        consentBar.querySelector('[data-consent-save]').hidden = !open;
186        consentBar.querySelector('[data-consent-toggle]').hidden = open;
187    }
188
189    if (consentBar) {
190        consentBar.addEventListener('click', function (event) {
191            if (event.target.closest('[data-consent-accept]')) {
192                saveConsent(true, true);
193            } else if (event.target.closest('[data-consent-reject]')) {
194                saveConsent(false, false);
195            } else if (event.target.closest('[data-consent-toggle]')) {
196                toggleConsentSettings(true);
197            } else if (event.target.closest('[data-consent-save]')) {
198                saveConsent(consentBar.querySelector('[data-consent-analytics]').checked, consentBar.querySelector('[data-consent-ads]').checked);
199            }
200        });
201        if (!window.hhConsent) {
202            showConsentBar(false);
203        }
204    }
205    document.addEventListener('click', function (event) {
206        if (event.target.closest('[data-consent-open]')) {
207            event.preventDefault();
208            showConsentBar(true);
209        }
210    });
211    if (window.hhConsent && window.hhConsent.analytics) {
212        loadToplist();
213    }
214
215    /* ---------- Game detail ---------- */
216
217    // light YouTube player: the iframe is created after click
218    function playVideo(player, videoId) {
219        player.dataset.videoId = videoId;
220        var iframe = document.createElement('iframe');
221        // enablejsapi lets GA4 enhanced measurement track video start/progress/complete
222        iframe.src = 'https://www.youtube-nocookie.com/embed/' + encodeURIComponent(videoId) + '?autoplay=1&rel=0&enablejsapi=1&origin=' + encodeURIComponent(location.origin);
223        iframe.title = 'Video';
224        iframe.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture';
225        iframe.allowFullscreen = true;
226        player.innerHTML = '';
227        player.appendChild(iframe);
228    }
229
230    document.addEventListener('click', function (event) {
231        var play = event.target.closest('[data-video-play]');
232        if (play) {
233            var player = play.closest('[data-video-player]');
234            track('video_play_click', {video_id: player.dataset.videoId});
235            playVideo(player, player.dataset.videoId);
236            return;
237        }
238
239        var select = event.target.closest('[data-video-select]');
240        if (select) {
241            var mainPlayer = document.querySelector('[data-video-player]');
242            if (!mainPlayer) {
243                return;
244            }
245            document.querySelectorAll('[data-video-select]').forEach(function (item) {
246                item.classList.toggle('is-active', item === select);
247            });
248            playVideo(mainPlayer, select.dataset.videoSelect);
249            mainPlayer.scrollIntoView({behavior: 'smooth', block: 'center'});
250        }
251    });
252
253    // gallery lightbox
254    var dialog = document.querySelector('[data-lightbox-dialog]');
255    if (dialog && typeof dialog.showModal === 'function') {
256        var items = Array.prototype.slice.call(document.querySelectorAll('[data-lightbox]'));
257        var image = dialog.querySelector('[data-lightbox-image]');
258        var current = 0;
259
260        var show = function (index) {
261            current = (index + items.length) % items.length;
262            image.src = items[current].getAttribute('href');
263            image.alt = items[current].querySelector('img') ? items[current].querySelector('img').alt : '';
264        };
265
266        items.forEach(function (item, index) {
267            item.addEventListener('click', function (event) {
268                event.preventDefault();
269                show(index);
270                dialog.showModal();
271            });
272        });
273        dialog.querySelector('[data-lightbox-close]').addEventListener('click', function () { dialog.close(); });
274        dialog.querySelector('[data-lightbox-prev]').addEventListener('click', function () { show(current - 1); });
275        dialog.querySelector('[data-lightbox-next]').addEventListener('click', function () { show(current + 1); });
276        dialog.addEventListener('click', function (event) {
277            if (event.target === dialog) {
278                dialog.close();
279            }
280        });
281        dialog.addEventListener('keydown', function (event) {
282            if (event.key === 'ArrowLeft') { show(current - 1); }
283            if (event.key === 'ArrowRight') { show(current + 1); }
284        });
285    }
286
287    // search suggestions (SearchController::suggest)
288    document.querySelectorAll('form.search[data-suggest-url]').forEach(function (form) {
289        var input = form.querySelector('input[name="q"]');
290        var list = form.querySelector('.search-suggest');
291        if (!input || !list || !window.fetch) {
292            return;
293        }
294        var cache = {};
295        var items = [];
296        var active = -1;
297        var timer = null;
298        var lastTerm = '';
299
300        function close() {
301            list.hidden = true;
302            list.innerHTML = '';
303            items = [];
304            active = -1;
305            input.setAttribute('aria-expanded', 'false');
306            input.removeAttribute('aria-activedescendant');
307        }
308
309        function setActive(index) {
310            var options = list.querySelectorAll('[role="option"]');
311            if (!options.length) {
312                return;
313            }
314            active = (index + options.length) % options.length;
315            options.forEach(function (option, i) {
316                option.setAttribute('aria-selected', i === active ? 'true' : 'false');
317            });
318            input.setAttribute('aria-activedescendant', options[active].id);
319        }
320
321        function scoreClass(rating) {
322            return rating >= 75 ? 'score--high' : (rating >= 50 ? 'score--mid' : 'score--low');
323        }
324
325        function render(term, data) {
326            if (term !== input.value.trim()) {
327                return;
328            }
329            items = data;
330            active = -1;
331            list.innerHTML = '';
332            if (!items.length) {
333                close();
334                return;
335            }
336            items.forEach(function (item, i) {
337                var li = document.createElement('li');
338                li.id = 'search-suggest-' + i;
339                li.setAttribute('role', 'option');
340                li.setAttribute('aria-selected', 'false');
341                var link = document.createElement('a');
342                link.href = item.url;
343                link.tabIndex = -1;
344                link.dataset.track = 'search_suggest_select';
345                link.dataset.trackSearchTerm = term;
346                link.dataset.trackPosition = String(i + 1);
347                var name = document.createElement('span');
348                name.className = 'search-suggest__name';
349                name.textContent = item.name;
350                var meta = document.createElement('span');
351                meta.className = 'search-suggest__meta';
352                var parts = [];
353                if (item.year) {
354                    parts.push(item.year);
355                }
356                if (item.reviews > 0) {
357                    parts.push(form.dataset.suggestReviews + ': ' + item.reviews);
358                }
359                meta.textContent = parts.join(' · ');
360                var body = document.createElement('span');
361                body.className = 'search-suggest__body';
362                body.appendChild(name);
363                body.appendChild(meta);
364                link.appendChild(body);
365                if (item.rating !== null) {
366                    var score = document.createElement('span');
367                    score.className = 'score score--sm ' + scoreClass(item.rating);
368                    score.textContent = item.rating + '%';
369                    link.appendChild(score);
370                }
371                li.appendChild(link);
372                list.appendChild(li);
373            });
374            list.hidden = false;
375            input.setAttribute('aria-expanded', 'true');
376        }
377
378        function load(term) {
379            if (cache[term]) {
380                render(term, cache[term]);
381                return;
382            }
383            fetch(form.dataset.suggestUrl + '?q=' + encodeURIComponent(term), {headers: {Accept: 'application/json'}})
384                .then(function (response) { return response.ok ? response.json() : {items: []}; })
385                .then(function (data) {
386                    cache[term] = data.items || [];
387                    render(term, cache[term]);
388                })
389                .catch(function () {});
390        }
391
392        input.addEventListener('input', function () {
393            var term = input.value.trim();
394            clearTimeout(timer);
395            if (term.length < 2) {
396                lastTerm = '';
397                close();
398                return;
399            }
400            if (term === lastTerm) {
401                return;
402            }
403            lastTerm = term;
404            timer = setTimeout(function () { load(term); }, 150);
405        });
406
407        input.addEventListener('keydown', function (event) {
408            if (list.hidden) {
409                return;
410            }
411            if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
412                event.preventDefault();
413                setActive(active + (event.key === 'ArrowDown' ? 1 : -1));
414            } else if (event.key === 'Enter' && active >= 0) {
415                event.preventDefault();
416                list.querySelectorAll('[role="option"] a')[active].click();
417            } else if (event.key === 'Escape') {
418                close();
419            }
420        });
421
422        // links are clicked before the input loses the focus
423        list.addEventListener('mousedown', function (event) {
424            event.preventDefault();
425        });
426        input.addEventListener('blur', close);
427        input.addEventListener('focus', function () {
428            if (input.value.trim().length >= 2 && input.value.trim() === lastTerm && cache[lastTerm]) {
429                render(lastTerm, cache[lastTerm]);
430            }
431        });
432    });
433
434    // countdown to the release date
435    document.querySelectorAll('[data-countdown]').forEach(function (element) {
436        var target = new Date(element.dataset.countdown).getTime();
437        var value = element.querySelector('[data-countdown-value]');
438        if (!value || isNaN(target)) {
439            return;
440        }
441        var update = function () {
442            var diff = Math.max(0, target - Date.now());
443            var days = Math.floor(diff / 86400000);
444            var hours = Math.floor(diff % 86400000 / 3600000);
445            var minutes = Math.floor(diff % 3600000 / 60000);
446            value.textContent = days + ' d ' + String(hours).padStart(2, '0') + ' h ' + String(minutes).padStart(2, '0') + ' min';
447        };
448        update();
449        setInterval(update, 30000);
450    });
451
452    // mini header of the game shown when the hero is not visible
453    var sticky = document.querySelector('[data-game-sticky]');
454    var hero = document.querySelector('[data-game-hero]');
455    if (sticky && hero && 'IntersectionObserver' in window) {
456        new IntersectionObserver(function (entries) {
457            var hidden = !entries[0].isIntersecting && entries[0].boundingClientRect.top < 0;
458            sticky.classList.toggle('is-visible', hidden);
459            sticky.setAttribute('aria-hidden', hidden ? 'false' : 'true');
460        }).observe(hero);
461    }
462})();

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.