PageSourceSearch

https://forgames.cz/wp-content/themes/abf-theme/assets/js/custom-endpoints.js

js forgames.cz collected 2026-10-08 23:36:26 UTC 168,386 bytes, 3,780 lines download raw bytes

1// Safe wrapper around decodeURIComponent: some exhibitor fields (e.g. profile
2// text containing a literal "%") are not valid percent-encoded strings and make
3// the native decodeURIComponent throw "URIError: URI malformed", which aborts
4// the whole list render. Fall back to the raw string when decoding fails.
5function safeDecodeURIComponent(str) {
6    if (str == null) return '';
7    try {
8        return decodeURIComponent(str);
9    } catch (e) {
10        return String(str);
11    }
12}
13
14function escapeHtml(str) {
15    return String(str == null ? '' : str)
16        .replace(/&/g, '&')
17        .replace(/</g, '&lt;')
18        .replace(/>/g, '&gt;')
19        .replace(/"/g, '&quot;')
20        .replace(/'/g, '&#39;');
21}
22
23// Sjednotí hodnotu haly na stabilní klíč. Data jsou nekonzistentní - hala je
24// zadaná jako "1" i jako "Hala 1"; obě vrátí "1". Pojmenované plochy
25// ("Vstupní hala I", "Venkovní plocha") se zachovají beze změny.
26// Musí odpovídat normalizaci v exhibition-plan.php.
27function normalizeHallKey(value) {
28    var s = safeDecodeURIComponent(value).replace(/\+/g, ' ').replace(/\s+/g, ' ').trim();
29    var m = s.match(/^(?:hala\s*)?(\d+)$/i);
30    return m ? m[1] : s;
31}
32
33var iconCache = {};
34var oboryFullData = null;
35var pendingExhibitorRestore = null;
36var planHallsData = null;
37var allExhibitorsData = null;
38var mainPlanImageUrl = '';
39var pendingHallRestore = null;
40var destinationsHierarchy = null;
41var pendingDestinationRestore = null;
42var catalogSources = Array.isArray(customBlockData.catalogSources) && customBlockData.catalogSources.length > 0
43    ? customBlockData.catalogSources
44    : [{ key: 'default', label: 'Aktuální veletrh', web_id: 0, web_url: '' }];
45var defaultCatalogSourceKey = customBlockData.defaultCatalogSource || 'default';
46
47function isValidCatalogSourceKey(key) {
48    return catalogSources.some(function (source) {
49        return source && source.key === key;
50    });
51}
52
53function resolveCatalogSourceKeyFromUrl() {
54    var urlParams = new URLSearchParams(window.location.search);
55    var requestedKey = urlParams.get('catalog');
56    return isValidCatalogSourceKey(requestedKey) ? requestedKey : defaultCatalogSourceKey;
57}
58
59var currentCatalogSourceKey = resolveCatalogSourceKeyFromUrl();
60
61function ensurePlanMapLightbox($context, imageUrl, title) {
62    if (!$context || $context.length === 0) return;
63
64    var $mapImg = $context.find('.plan-map-image').first();
65    if ($mapImg.length === 0) return;
66
67    var src = imageUrl || $mapImg.attr('src');
68    if (!src) return;
69
70    var $link = $mapImg.parent('a.plan-map-lightbox');
71    if ($link.length === 0) {
72        $mapImg.wrap('<a class="plan-map-lightbox" data-lightbox="catalog-plan-map" href="#"></a>');
73        $link = $mapImg.parent('a.plan-map-lightbox');
74    }
75
76    $link.attr({
77        href: src,
78        'data-lightbox': 'catalog-plan-map',
79        'data-title': title || 'Plánek výstaviště',
80        'aria-label': 'Otevřít plánek ve větším náhledu'
81    }).css('display', 'block');
82
83    $mapImg.css('cursor', 'zoom-in');
84}
85
86function syncPlanScrollablePanels($panel) {
87    if (!$panel || $panel.length === 0) return;
88
89    var $scrollPanels = $panel.find('.plan-halls-grid, .plan-hall-detail');
90    if ($scrollPanels.length === 0) return;
91
92    var isMobile = window.matchMedia('(max-width: 767px)').matches;
93
94    if (isMobile) {
95        $scrollPanels.css({
96            'max-height': 'none',
97            'overflow-y': 'visible'
98        });
99        return;
100    }
101
102    var imgHeight = $panel.find('.plan-map-image').height();
103    $scrollPanels.css('overflow-y', 'auto');
104
105    if (imgHeight > 0) {
106        $scrollPanels.css('max-height', imgHeight + 'px');
107    } else {
108        $scrollPanels.css('max-height', '');
109    }
110}
111
112function updateCatalogUrl(params) {
113    var url = new URL(window.location);
114    ['catalog', 'tab', 'obor', 'country', 'brand', 'destination', 'continent', 'exhibitor', 'hall'].forEach(function (key) {
115        url.searchParams.delete(key);
116    });
117    if (isValidCatalogSourceKey(currentCatalogSourceKey) && currentCatalogSourceKey !== defaultCatalogSourceKey) {
118        url.searchParams.set('catalog', currentCatalogSourceKey);
119    }
120    Object.keys(params).forEach(function (key) {
121        if (params[key] != null && params[key] !== '') {
122            url.searchParams.set(key, params[key]);
123        }
124    });
125    history.replaceState(null, '', url.toString());
126}
127
128// Sestaví URL na katalog se zadanými filtry (zachová page_id apod., přepíše jen filtrovací parametry).
129function buildCatalogFilterUrl(params) {
130    var url = new URL(window.location);
131    ['catalog', 'tab', 'obor', 'country', 'brand', 'destination', 'continent', 'exhibitor', 'hall'].forEach(function (key) {
132        url.searchParams.delete(key);
133    });
134    if (isValidCatalogSourceKey(currentCatalogSourceKey) && currentCatalogSourceKey !== defaultCatalogSourceKey) {
135        url.searchParams.set('catalog', currentCatalogSourceKey);
136    }
137    Object.keys(params).forEach(function (key) {
138        if (params[key] != null && params[key] !== '') {
139            url.searchParams.set(key, params[key]);
140        }
141    });
142    return url.toString();
143}
144
145function renderCatalogSourceTabs() {
146    if (!Array.isArray(catalogSources) || catalogSources.length <= 1) return;
147
148    $('.content-container--katalog-vystavovatelu .newsletter-card-wrapper').each(function () {
149        var $wrapper = $(this);
150        var $tabsHost = $wrapper.parent();
151        if ($tabsHost.find('.js-katalog-source-tabs').length > 0) return;
152
153        var html = '<div class="catalog-source-tabs-shell"><div class="catalog-source-tabs js-katalog-source-tabs" role="tablist" aria-label="Přepnutí katalogu vystavovatelů">';
154        catalogSources.forEach(function (source) {
155            if (!source || !source.key) return;
156            var activeClass = source.key === currentCatalogSourceKey ? ' is-active' : '';
157            var isSelected = source.key === currentCatalogSourceKey ? 'true' : 'false';
158            html += '<button type="button" role="tab" aria-selected="' + isSelected + '" class="catalog-source-tab js-katalog-source-tab' + activeClass + '" data-catalog-source="' + escapeHtml(source.key) + '">' + escapeHtml(source.label || source.key) + '</button>';
159        });
160        html += '</div></div>';
161
162        $tabsHost.prepend(html);
163    });
164}
165
166// Najde kontinent, pod který destinace patří (podle názvu) v načtené hierarchii destinací.
167function getContinentForDestination(destName) {
168    if (!destinationsHierarchy) return '';
169    var found = '';
170    Object.keys(destinationsHierarchy).forEach(function (continentName) {
171        if (found) return;
172        var dests = destinationsHierarchy[continentName] || [];
173        for (var i = 0; i < dests.length; i++) {
174            if (dests[i].name === destName) { found = continentName; break; }
175        }
176    });
177    return found;
178}
179
180// Přepne katalog na daný tab a obnoví zobrazení seznamu (in-place, bez reloadu).
181// Zrcadlí reset logiku z initKatalogTabs i přepnutí is-active z toggle-znacky.js,
182// funguje spolehlivě i když je cílový tab už aktivní. Vrací $list-with-filter.
183function activateKatalogTab($interactableWrapper, tab) {
184    var $tabsWrap = $interactableWrapper.find('.js-katalog-tabs');
185    if ($tabsWrap.length === 0) {
186        $tabsWrap = $interactableWrapper.closest('.newsletter-card-wrapper').find('.js-katalog-tabs');
187    }
188    // Aktivní tab (jako toggle-znacky.js)
189    $tabsWrap.find('[data-tab]').removeClass('is-active');
190    $tabsWrap.find('[data-tab="' + tab + '"]').addClass('is-active');
191    $tabsWrap.find('label[data-tab="' + tab + '"] input[type="radio"]').prop('checked', true);
192
193    var $listFilter = $interactableWrapper.find('.list-with-filter');
194
195    // Reset: skrýt detaily, zobrazit seznam (jako initKatalogTabs)
196    $interactableWrapper.find('.field-detail-wrapper').remove();
197    $interactableWrapper.find('.obory-nav').remove();
198    $interactableWrapper.find('.obory-exhibitors').remove();
199    $interactableWrapper.find('.plan-hall-topbar').remove();
200    $interactableWrapper.find('.plan-hall-detail').remove();
201    $interactableWrapper.find('.company-detail-wrapper-new').hide();
202    if ($listFilter.is(':hidden')) {
203        $listFilter.css('display', '');
204    }
205
206    // Přepnout panely
207    $listFilter.find('.js-katalog-panel').addClass('is-hidden');
208    $listFilter.find('.js-katalog-panel[data-panel="' + tab + '"]').removeClass('is-hidden');
209
210    // Skrýt vyhledávání/řazení pro ne-companies taby
211    $listFilter.find('.headline-and-search').hide();
212    $listFilter.children('.sort-buttons-wrapper').hide();
213    $listFilter.children('.filter-reset').hide();
214
215    return $listFilter;
216}
217
218function setExhibitorParam(name) {
219    var url = new URL(window.location);
220    if (name) {
221        url.searchParams.set('exhibitor', name);
222    } else {
223        url.searchParams.delete('exhibitor');
224    }
225    history.replaceState(null, '', url.toString());
226}
227
228function restoreFromUrl() {
229    var urlParams = new URLSearchParams(window.location.search);
230    var tab = urlParams.get('tab');
231    var obor = urlParams.get('obor');
232    var country = urlParams.get('country');
233    var brand = urlParams.get('brand');
234    var destination = urlParams.get('destination');
235    var continent = urlParams.get('continent');
236    var exhibitor = urlParams.get('exhibitor');
237    var hall = urlParams.get('hall');
238
239    if (!tab && !exhibitor) return;
240
241    // Programmatically select the tab (native click so toggle-znacky.js is-active handler fires too)
242    if (tab) {
243        var $tabLabel = $('.js-katalog-tabs label[data-tab="' + tab + '"]');
244        if ($tabLabel.length > 0) {
245            $tabLabel[0].click();
246        }
247    }
248
249    // Set pending exhibitor restore (consumed after AJAX-loaded lists render)
250    if (exhibitor) {
251        pendingExhibitorRestore = exhibitor;
252    }
253
254    // Restore filter state
255    if (tab === 'fields' && obor) {
256        var $panel = $('.js-katalog-panel[data-panel="fields"]');
257        if ($panel.length > 0) {
258            renderFieldsLevel($panel, obor);
259            updateCatalogUrl({ tab: 'fields', obor: obor });
260        }
261    } else if (tab === 'countries' && country) {
262        $('.catalog-filter-detail--box.country-filter').filter(function () {
263            return $(this).data('name') === country;
264        }).first().trigger('click');
265    } else if (tab === 'brands' && brand) {
266        $('.catalog-filter-detail--box.brand-filter').filter(function () {
267            return $(this).data('name') === brand;
268        }).first().trigger('click');
269    } else if (tab === 'destinations' && continent) {
270        // Nastavit před triggerem, aby continent handler proměnnou viděl
271        if (destination) {
272            pendingDestinationRestore = destination;
273        }
274        $('.catalog-filter-detail--box.continent-filter').filter(function () {
275            return $(this).data('name') === continent;
276        }).first().trigger('click');
277    } else if (tab === 'plan' && hall) {
278        // Plan data loads asynchronously — set pending restore so appendPlanPanel auto-clicks the hall
279        pendingHallRestore = hall;
280    }
281
282    // For companies tab (or no tab), exhibitors are already rendered — open detail directly
283    if (exhibitor && (!tab || tab === 'companies')) {
284        $('.company-detail-katalog-link-new').each(function () {
285            var companyData = $(this).data('company');
286            if (companyData && companyData.name === exhibitor) {
287                var $el = $(this);
288                var $detail = $el.closest('.newsletter-card-wrapper').find('.company-detail-wrapper-new');
289                if ($detail.length === 0) {
290                    $detail = $('.company-detail-wrapper-new');
291                }
292                $detail.data('navigation-source', 'list');
293                $el.closest('.interactable-part-wrapper-catalog').find('.list-with-filter').hide();
294                $detail.css({ display: 'flex', opacity: 1 });
295                populateCompanyDetail(companyData, $detail);
296                pendingExhibitorRestore = null;
297                return false;
298            }
299        });
300    }
301}
302
303$(document).ready(function () {
304    const fieldListWrapper = $('.field-lists-wrapper');
305    const partnerWrapper = $('.logo-sections-wrapper');
306    const exhibitorTicket = $('.zbyva-vstupenek');
307    const mainPlanImage = $('.main-plan-image');
308    let hasCategoryLoadedFromUrl = false;
309    const isNewLayout = $('.js-katalog-tabs').length > 0;
310
311    newsletterFormSubmit();
312    exhibitorFormSubmit();
313    contactFormSubmit();
314    akreditaceFormSubmit();
315    exhibitorTicketFormSubmit();
316    fetchFields();
317    checkAkreditaceOtevrena();
318    exhibitorMainPlan();
319    exhibitorTicketDetail();
320    loadRSSfeeds();
321
322    if (isNewLayout) {
323        renderCatalogSourceTabs();
324        initKatalogTabs();
325    }
326
327    function exhibitorMainPlan() {
328        if (mainPlanImage.length > 0) {
329            const url = window.location.href;
330            const match = url.match(/historie-veletrhu\/.*?(\d{4}).*?/);
331            if (match) {
332                const year = match[1];               // "2024"
333                const lastTwoDigits = year.slice(-2); // "24"
334                $.ajax({
335                    url: '/wp-json/api/v1/exhibitor-plan-image',
336                    method: 'POST',
337                    data: {
338                        history: lastTwoDigits,
339                        catalog: currentCatalogSourceKey
340                    },
341                    success: function (response) {
342                        const file = JSON.parse(response);
343                        mainPlanImage.attr('src', `https://is.abf.cz/file/raster?nl_id=${file.id}&s_format_to=png`)
344                    },
345                    error: function () {
346                        console.log('Error fetching exhibitors from the API...');
347                    }
348                })
349            } else {
350                $.ajax({
351                    url: '/wp-json/api/v1/exhibitor-plan-image',
352                    method: 'POST',
353                    data: {
354                        catalog: currentCatalogSourceKey
355                    },
356                    success: function (response) {
357                        const file = JSON.parse(response);
358                        mainPlanImage.attr('src', `https://is.abf.cz/file/raster?nl_id=${file.id}&s_format_to=png`)
359                    },
360                    error: function () {
361                        console.log('Error fetching exhibitors from the API...');
362                    }
363                })
364            }
365        }
366    }
367
368    function exhibitorTicketDetail() {
369        if (exhibitorTicket.length > 0) {
370            const urlParams = new URLSearchParams(window.location.search);
371            const vParam = urlParams.get('v');
372            $.ajax({
373                url: '/wp-json/api/v1/exhibitor-ticket-detail',
374                data: {
375                    id: vParam
376                },
377                method: 'POST',
378                success: function (response) {
379                    let result = JSON.parse(response);
380                    if (result.s_x_nazev) {
381                        let name = safeDecodeURIComponent(result.s_x_nazev).replace(/\+/g, ' ');
382                        $('.h1.secondary').text('VSTUPENKA OD ' + name);
383                    }
384                },
385                error: function () {
386                    console.log('Error fetching exhibitors from the API...');
387                }
388            })
389        }
390    }
391
392    function fetchFields() {
393        if (fieldListWrapper.length > 0) {
394            const url = window.location.href;
395            const match = url.match(/historie-veletrhu\/.*?(\d{4}).*?/);
396            if (match) {
397                const year = match[1];               // "2024"
398                const lastTwoDigits = year.slice(-2); // "24"
399                $.ajax({
400                    url: '/wp-json/api/v1/oborove-cleneni',
401                    data: {
402                        oboryLoadNeeded: true,
403                        history: lastTwoDigits,
404                        catalog: currentCatalogSourceKey
405                    },
406                    method: 'POST',
407                    beforeSend: function (xhr) {
408                        fieldListWrapper.append('<div>Načítání...</div>');
409                    },
410                    success: function (response) {
411                        const obory = response['oborove-cleneni'];
412                        partnerWrapper.find('div:contains("Načítání...")').remove(); // Remove loading indicators
413
414                        appendFieldListObory(fieldListWrapper, obory);
415                    },
416                    error: function () {
417                        console.log('Error fetching partners from the API...');
418                    }
419                })
420            } else {
421                $.ajax({
422                    url: '/wp-json/api/v1/oborove-cleneni',
423                    data: {
424                        oboryLoadNeeded: true,
425                        catalog: currentCatalogSourceKey
426                    },
427                    method: 'POST',
428                    beforeSend: function (xhr) {
429                        fieldListWrapper.append('<div>Načítání...</div>');
430                    },
431                    success: function (response) {
432                        const obory = response['oborove-cleneni'];
433                        partnerWrapper.find('div:contains("Načítání...")').remove(); // Remove loading indicators
434
435                        appendFieldListObory(fieldListWrapper, obory);
436                    },
437                    error: function () {
438                        console.log('Error fetching partners from the API...');
439                    }
440                })
441            }
442        }
443    }
444
445    const planListWrapper = $('ul.companies-list.plan');
446
447    function fetchExhibitorsPlan(searchTerm = '', hallLoadNeeded = true, selectedHall = '') {
448
449        const url = window.location.href;
450        const match = url.match(/historie-veletrhu\/.*?(\d{4}).*?/);
451
452        function isEmptyObject(obj) {
453            for (let key in obj) {
454                if (obj.hasOwnProperty(key)) {
455                    if (typeof obj[key] === 'object' && obj[key] !== null) {
456                        for (let nestedKey in obj[key]) {
457                            if (obj[key][nestedKey] !== "") {
458                                return false;
459                            }
460                        }
461                    } else {
462                        return false;
463                    }
464                }
465            }
466            return true;
467        }
468        if (planListWrapper.length > 0) {
469            if (match) {
470                const year = match[1];               // "2024"
471                const lastTwoDigits = year.slice(-2); // "24"
472                $.ajax({
473                    url: '/wp-json/api/v1/exhibition-plan',
474                    method: 'POST',
475                    data: {
476                        search: searchTerm, // Pass the search term to the server
477                        hallLoadNeeded: hallLoadNeeded, // Pass the hall load needed flag to the server
478                        selectedHall: selectedHall, // Pass the selected hall to the server
479                        history: lastTwoDigits,
480                        catalog: currentCatalogSourceKey
481                    },
482                    beforeSend: function (xhr) {
483                        planListWrapper.append('<div>Načítání...</div>');
484                    },
485                    success: function (response) {
486                        const exhibitors = response['seznam-vystavovatelu'];
487                        const haly = response['haly'];
488
489                        if (isEmptyObject(exhibitors) && searchTerm === '' && selectedHall === '' && hallLoadNeeded) {
490                            $('.interactable-part-wrapper').hide();
491                        } else {
492                            $('.interactable-part-wrapper').show();
493                            if (hallLoadNeeded) {
494                                appendHallFilter($('.filter-category-wrapper'), haly);
495                            }
496                            appendExhibitorPlan(planListWrapper, exhibitors, haly);
497
498                            if (isEmptyObject(exhibitors)) {
499                                planListWrapper.empty();
500                                planListWrapper.append('<div>Nebyl nalezen žádný vystavovatel.</div>');
501                            }
502                        }
503                    },
504                })
505            } else {
506                $.ajax({
507                    url: '/wp-json/api/v1/exhibition-plan',
508                    method: 'POST',
509                    data: {
510                        search: searchTerm, // Pass the search term to the server
511                        hallLoadNeeded: hallLoadNeeded, // Pass the hall load needed flag to the server
512                        selectedHall: selectedHall, // Pass the selected hall to the server
513                        catalog: currentCatalogSourceKey,
514                    },
515                    beforeSend: function (xhr) {
516                        planListWrapper.append('<div>Načítání...</div>');
517                    },
518                    success: function (response) {
519                        const exhibitors = response['seznam-vystavovatelu'];
520                        const haly = response['haly'];
521
522                        if (isEmptyObject(exhibitors) && searchTerm === '' && selectedHall === '' && hallLoadNeeded) {
523                            $('.interactable-part-wrapper').hide();
524                        } else {
525                            $('.interactable-part-wrapper').show();
526                            if (hallLoadNeeded) {
527                                appendHallFilter($('.filter-category-wrapper'), haly);
528                            }
529                            appendExhibitorPlan(planListWrapper, exhibitors, haly);
530
531                            if (isEmptyObject(exhibitors)) {
532                                planListWrapper.empty();
533                                planListWrapper.append('<div>Nebyl nalezen žádný vystavovatel.</div>');
534                            }
535                        }
536                    },
537                })
538            }
539        }
540    }
541
542
543    const filtersWrapper = $('#obory-filters-list-wrapper');
544    const znackyfiltersWrapper = $('#znacky-filters-list-wrapper');
545    const exhibitorListWrapper = $('ul.companies-list.katalog');
546    let selectedCategories = [];
547    let selectedZnacky = [];
548    let selectedCountries = [];
549    let selectedDestinations = [];
550    let selectedHall = '';
551
552    function fetchExhibitors(searchTerm = '', categories = [], oboryLoadNeeded = true, znacky = [], countries = [], destinations = []) {
553        if (filtersWrapper.length > 0 || (isNewLayout && exhibitorListWrapper.length > 0)) {
554
555            // Pokud máme cache a nepotřebujeme načíst panel filtrů, filtrujeme JS-side
556            if (!oboryLoadNeeded && allExhibitorsData !== null) {
557                var filtered = filterExhibitorsFromCache({
558                    search: searchTerm,
559                    categories: categories,
560                    znacky: znacky,
561                    countries: countries,
562                    destinations: destinations,
563                });
564                exhibitorListWrapper.empty();
565                if (isNewLayout) {
566                    appendExhibitorListNew(exhibitorListWrapper, filtered, true, 'name');
567                } else {
568                    appendExhibitorList(exhibitorListWrapper, filtered, true, 'name');
569                }
570                return;
571            }
572
573            $.ajax({
574                url: '/wp-json/api/v1/oborove-cleneni',
575                method: 'POST',
576                data: {
577                    search: searchTerm, // Pass the search term to the server
578                    categories: categories, // Pass the selected categories to the server
579                    oboryLoadNeeded: oboryLoadNeeded,
580                    znacky: znacky,
581                    countries: countries,
582                    destinations: destinations,
583                    catalog: currentCatalogSourceKey
584                },
585                beforeSend: function (xhr) {
586                    exhibitorListWrapper.empty();
587                    exhibitorListWrapper.append('<div>Načítání...</div>');
588
589                    if (oboryLoadNeeded && filtersWrapper.length > 0) {
590                        filtersWrapper.append('<div>Načítání...</div>');
591                        znackyfiltersWrapper.append('<div>Načítání...</div>');
592                    }
593                },
594                success: function (response) {
595                    exhibitorListWrapper.find('div:contains("Načítání...")').remove();
596                    if (filtersWrapper.length > 0) {
597                        filtersWrapper.find('div:contains("Načítání...")').remove();
598                        znackyfiltersWrapper.find('div:contains("Načítání...")').remove();
599                    }
600
601                    const dataOboroveCleneni = response['oborove-cleneni'];
602                    const dataZnacky = response['znacky'];
603                    const dataCountries = response['staty'];
604                    const dataDestinace = response['destinace_hierarchy'];
605                    const dataSeznamVystavovatelu = response['seznam-vystavovatelu'];
606                    allExhibitorsData = dataSeznamVystavovatelu;
607
608                    let isAscending = true;
609                    let sortBy = 'name';
610
611                    // Sort by name (alphabetical)
612                    $('.alphabet-sort').on('click', function () {
613                        isAscending = !isAscending;
614                        sortBy = 'name';
615                        updateSortIcons(isAscending, $(this));
616                        if (isNewLayout) {
617                            appendExhibitorListNew(exhibitorListWrapper, dataSeznamVystavovatelu, isA
617scending, sortBy);
618                        } else {
619                            appendExhibitorList(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy);
620                        }
621                    });
622
623                    // Sort by hall (s_hala)
624                    $('.hall-sort').on('click', function () {
625                        isAscending = !isAscending;
626                        sortBy = 'hala';
627                        updateSortIcons(isAscending, $(this));
628                        if (isNewLayout) {
629                            appendExhibitorListNew(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy);
630                        } else {
631                            appendExhibitorList(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy);
632                        }
633                    });
634
635                    // Initial rendering of the exhibitor list (ascending by default)
636                    if (isNewLayout) {
637                        appendExhibitorListNew(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy);
638                        if (oboryLoadNeeded) {
639                            appendFieldsPanelNew(dataOboroveCleneni);
640                            appendCountries(dataCountries);
641                            appendBrandsPanel(dataZnacky);
642                            destinationsHierarchy = dataDestinace || null;
643                            appendDestinationsPanel(dataDestinace);
644
645                            // Show tabs that have data (hidden by default)
646                            var tabChecks = {
647                                'fields': dataOboroveCleneni && Object.keys(dataOboroveCleneni).length > 0,
648                                'countries': dataCountries && dataCountries.length > 0,
649                                'brands': dataZnacky && Object.keys(dataZnacky).length > 0,
650                                'destinations': dataDestinace && Object.keys(dataDestinace).length > 0
651                            };
652                            $.each(tabChecks, function (tab, hasData) {
653                                if (hasData) {
654                                    $('.js-katalog-tabs label[data-tab="' + tab + '"]').show();
655                                }
656                            });
657
658                            // Fetch plan data (halls + main plan image) for the plan tab
659                            fetchPlanData();
660                        }
661                    } else {
662                        appendExhibitorList(exhibitorListWrapper, dataSeznamVystavovatelu, isAscending, sortBy);
663                        if (oboryLoadNeeded) {
664                            appendOboroveCleneni(filtersWrapper, dataOboroveCleneni);
665                            appendZnacky(znackyfiltersWrapper, dataZnacky);
666                        }
667                    }
668
669                    // Check for URL parameter 'category', but ensure it runs only once
670                    if (!hasCategoryLoadedFromUrl) {
671                        checkUrlForCategory();
672                        restoreFromUrl();
673                        hasCategoryLoadedFromUrl = true; // Prevent additional calls
674                    }
675                },
676                error: function () {
677                    console.log('Error fetching exhibitors from the API...');
678                }
679            });
680        }
681    }
682
683    function checkUrlForCategory() {
684        const urlParams = new URLSearchParams(window.location.search);
685        const categoryFromUrl = urlParams.get('category');
686
687        if (categoryFromUrl) {
688            const matchingCheckbox = $('input[type="checkbox"][data-slug="' + categoryFromUrl + '"]');
689
690            if (matchingCheckbox.length) {
691                matchingCheckbox.prop('checked', true);
692                matchingCheckbox.closest('.w-checkbox').find('.w-checkbox-input.w-checkbox-input--inputType-custom').addClass('w--redirected-checked');
693
694                selectedCategories.push(matchingCheckbox.data('name'));
695
696                fetchExhibitors($('.search-input').val().trim(), selectedCategories, false);
697            }
698        }
699    }
700
701    function switchCatalogSource(sourceKey) {
702        if (!isValidCatalogSourceKey(sourceKey) || sourceKey === currentCatalogSourceKey) {
703            return;
704        }
705
706        currentCatalogSourceKey = sourceKey;
707        allExhibitorsData = null;
708        planHallsData = null;
709        mainPlanImageUrl = '';
710        destinationsHierarchy = null;
711        pendingDestinationRestore = null;
712        pendingHallRestore = null;
713        pendingExhibitorRestore = null;
714        selectedCategories = [];
715        selectedZnacky = [];
716        selectedCountries = [];
717        selectedDestinations = [];
718        selectedHall = '';
719
720        $('.js-katalog-source-tab').removeClass('is-active').attr('aria-selected', 'false');
721        $('.js-katalog-source-tab[data-catalog-source="' + sourceKey + '"]').addClass('is-active').attr('aria-selected', 'true');
722
723        $('.companies-list.katalog input[type="checkbox"], .list-with-filter input[type="checkbox"]').prop('checked', false);
724        $('.w-checkbox-input--inputType-custom').removeClass('w--redirected-checked');
725        $('.filter-category-wrapper label').removeClass('is-active');
726
727        $('.interactable-part-wrapper-catalog').each(function () {
728            var $wrapper = $(this);
729            var $listFilter = activateKatalogTab($wrapper, 'companies');
730            $listFilter.find('.js-katalog-panel[data-panel!="companies"]').addClass('is-hidden');
731            $listFilter.find('.js-katalog-panel[data-panel="companies"]').removeClass('is-hidden');
732            $listFilter.find('.field-detail-wrapper, .obory-nav, .obory-exhibitors, .plan-hall-topbar, .plan-hall-detail').remove();
733            $wrapper.find('.company-detail-wrapper-new').hide();
734            $wrapper.find('.list-with-filter').show();
735            $wrapper.find('.js-katalog-tabs label[data-tab!="companies"]').hide();
736        });
737
738        var searchValue = $('.search-input').length ? ($('.search-input').val() || '').trim() : '';
739        var planSearchValue = $('.search-input-plan').length ? ($('.search-input-plan').val() || '').trim() : '';
740
741        fetchExhibitors(searchValue, [], true, [], [], []);
742        fetchExhibitorsPlan(planSearchValue, true, '');
743        updateCatalogUrl({});
744    }
745
746    $(document).on('click', '.js-katalog-source-tab', function (e) {
747        e.preventDefault();
748        switchCatalogSource($(this).data('catalog-source'));
749    });
750
751    // Initial fetch without search term
752    fetchExhibitors();
753    fetchExhibitorsPlan();
754
755    $(document).on('change', 'input[type="radio"]', function () {
756        // Skip tab radio buttons in the new layout
757        if ($(this).closest('.js-katalog-tabs').length > 0) return;
758
759        selectedHall = $(this).val().trim(); // Get the selected hall from the radio input
760
761        $(this).closest('label').addClass('is-active').siblings().removeClass('is-active');
762
763        // Re-fetch exhibitors with the selected hall
764        fetchExhibitorsPlan($('.search-input-plan').val().trim(), false, selectedHall);
765    });
766
767
768    // Trigger the fetch function on search input
769    $('.search-input').on('input', debounce(function () {
770        searchTerm = $(this).val().trim(); // Get the search term from the input
771        fetchExhibitors(searchTerm, selectedCategories, false, selectedZnacky, selectedCountries, selectedDestinations); // Re-fetch the exhibitors with the search term
772    }, 300));
773
774    $('.search-input-plan').on('input', debounce(function () {
775        searchTerm = $(this).val().trim(); // Get the search term from the input
776        fetchExhibitorsPlan(searchTerm, false, selectedHall); // Re-fetch the exhibitors with the search term
777    }, 300));
778
779    if (filtersWrapper.length > 0) {
780        $(document).on('change', 'input[type="checkbox"]', function () {
781            selectedCategories = [];
782            selectedZnacky = [];
783            selectedCountries = [];
784            selectedDestinations = [];
785
786            // Gather all checked categories
787            $('input[type="checkbox"]:checked').not('.znacky-input-c
787heckbox').each(function () {
788                selectedCategories.push($(this).data('name'));
789            });
790
791
792
793            $('input[type="checkbox"].znacky-input-checkbox:checked').each(function () {
794                selectedZnacky.push($(this).data('name'));
795            });
796
797            // Re-fetch exhibitors with the selected categories
798            fetchExhibitors($('.search-input').val().trim(), selectedCategories, false, selectedZnacky, selectedCountries, selectedDestinations);
799        });
800    }
801
802    // Handle country filter clicks - show field detail with exhibitor list
803    $(document).on('click', '.catalog-filter-detail--box.country-filter', function () {
804        const countryName = $(this).data('name');
805        const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog');
806
807        updateCatalogUrl({ tab: 'countries', country: countryName });
808
809        // Hide the list-with-filter
810        $interactableWrapper.find('.list-with-filter').hide();
811        $interactableWrapper.find('.company-detail-wrapper-new').hide();
812
813        // Remove any existing field detail wrapper
814        $interactableWrapper.find('.field-detail-wrapper').remove();
815
816        // Create field detail wrapper
817        const fieldDetailHTML = `
818            <div class="field-detail-wrapper" style="display: flex; flex-direction: column; gap: 16px;">
819                <a href="#" class="back-katalog-button w-inline-block">
820                    <div class="back-icon w-embed">
821                        <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
822                            <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
823                        </svg>
824                    </div>
825                    <div>Zpět</div>
826                </a>
827                <h2 class="fields-headline">${countryName}</h2>
828                <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div>
829                <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul>
830            </div>
831        `;
832
833        $interactableWrapper.append(fieldDetailHTML);
834
835        const $fieldDetailList = $interactableWrapper.find('.field-detail-exhibitor-list');
836        renderCachedExhibitors($fieldDetailList, { countries: [countryName] });
837    });
838
839    // Handle brand filter clicks - show field detail with exhibitor list
840    $(document).on('click', '.catalog-filter-detail--box.brand-filter', function () {
841        const brandName = $(this).data('name');
842        const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog');
843
844        updateCatalogUrl({ tab: 'brands', brand: brandName });
845
846        // Hide the list-with-filter
847        $interactableWrapper.find('.list-with-filter').hide();
848        $interactableWrapper.find('.company-detail-wrapper-new').hide();
849
850        // Remove any existing field detail wrapper
851        $interactableWrapper.find('.field-detail-wrapper').remove();
852
853        // Create field detail wrapper
854        const fieldDetailHTML = `
855            <div class="field-detail-wrapper" style="display: flex; flex-direction: column; gap: 16px;">
856                <a href="#" class="back-katalog-button w-inline-block">
857                    <div class="back-icon w-embed">
858                        <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
859                            <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
860                        </svg>
861                    </div>
862                    <div>Zpět</div>
863                </a>
864                <h2 class="fields-headline">${brandName}</h2>
865                    <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed">
865<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div>
866                <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul>
867            </div>
868        `;
869
870        $interactableWrapper.append(fieldDetailHTML);
871
872        const $fieldDetailList = $interactableWrapper.find('.field-detail-exhibitor-list');
873        renderCachedExhibitors($fieldDetailList, { znacky: [brandName] });
874    });
875
876    // Helper: returns sub-destinations one level below parentCode within a continent.
877    // parentCode = null → returns all top-level destinations for the continent.
878    // parentCode = "9.1." → returns destinations whose code starts with "9.1." and has one more segment (e.g. "9.1.1.").
879    function getSubDestinations(continentName, parentCode) {
880        if (!destinationsHierarchy || !destinationsHierarchy[continentName]) return [];
881        const allDests = destinationsHierarchy[continentName];
882        if (!parentCode) return allDests;
883
884        const parentParts = parentCode.replace(/\.$/, '').split('.');
885        return allDests.filter(function (dest) {
886            const parts = dest.code.replace(/\.$/, '').split('.');
887            if (parts.length !== parentParts.length + 1) return false;
888            for (var i = 0; i < parentParts.length; i++) {
889                if (parts[i] !== parentParts[i]) return false;
890            }
891            return true;
892        });
893    }
894
895    // Helper: build sub-destination boxes HTML
896    function buildSubDestsHTML(subDests, continentName) {
897        if (!subDests || subDests.length === 0) return '';
898        var html = '';
899        subDests.forEach(function (dest) {
900            html += `<div class="catalog-filter-detail--box destination-filter" data-name="${dest.name}" data-code="${dest.code}" data-continent="${continentName}" style="cursor: pointer;"><div class="img"><img class="img" src="${customBlockData.pluginUrl}images/files.svg" /></div><span class="list-item">${dest.code ? dest.code + ' ' : ''}${dest.name}</span></div>`;
901        });
902        return `<div class="catalog-filter-detail destination-sub-boxes">${html}</div>`;
903    }
904
905    // ─── Client-side exhibitor cache helpers ────────────────────────────────────
906
907    // Filtruje allExhibitorsData podle zadaných kritérií (stejná logika jako PHP backend).
908    // filters: { search, categories, znacky, countries, destinations, continents }
909    function filterExhibitorsFromCache(filters) {
910        if (!allExhibitorsData) return null; // cache ještě není k dispozici
911
912        var search      = filters.search      ? filters.search.toString().toLowerCase() : '';
913        var categories  = filters.categories  || [];
914        var znacky      = (filters.znacky     || []).map(function (z) { return z.toLowerCase().trim(); });
915        var countries   = filters.countries   || [];
916        var destinations = filters.destinations || [];
917        var continents  = filters.continents  || [];
918
919        var result = {};
920        Object.keys(allExhibitorsData).forEach(function (key) {
921            var ex = allExhibitorsData[key];
922
923            // Vyhledávání v názvu, e-mailu, telefonu, adrese a popisu
924            if (search) {
925                var decodeField = function (val) {
926                    var s = (val || '').toString().toLowerCase();
927                    try { s = safeDecodeURIComponent(s.replace(/\+/g, ' ')); } catch (e) {}
928                    return s;
929                };
930                var haystack = [
931                    ex.s_nazev,
932                    ex.s_email,
933                    ex.s_tel1,
934                    ex.s_street,
935                    ex.s_city,
936                    ex.s_zip,
937                    ex.s_profil
938                ].map(decodeField).join(' ');
939                if (haystack.indexOf(search) === -1) return;
940            }
941
942            // Filtr podle kategorie (kódy v čárkou oddělené hodnotě s_oborove_cleneni)
943            if (categories.length > 0) {
944                var exCats = (ex.s_oborove_cleneni || '').split(',').map(function (c) { return c.trim(); });
945                if (!categories.some(function (cat) { return exCats.indexOf(cat) !== -1; })) return;
946            }
947
948            // Filtr podle značky
949            if (znacky.length > 0) {
950                var exBrands = (ex.znacky_names || []).map(function (b) { return b.toLowerCase().trim(); });
951                if (!znacky.some(function (z) { return exBrands.indexOf(z) !== -1; })) return;
952            }
953
954            // Filtr podle státu
955            if (countries.length > 0) {
956                if (countries.indexOf(ex.s_stat) === -1) return;
957            }
958
959            // Filtr podle destinace
960            if (destinations.length > 0) {
961                var exDests = ex.destinace_names || [];
962                if (!destinations.some(function (d) { return exDests.indexOf(d) !== -1; })) return;
963            }
964
965            // Filtr podle kontinentu
966            if (continents.length > 0) {
967                var exConts = ex.destinace_continents || [];
968                if (!continents.some(function (c) { return exConts.indexOf(c) !== -1; })) return;
969            }
970
971            result[key] = ex;
972        });
973        return result;
974    }
975
976    // Filtruje cache a vykreslí výsledek do $fieldDetailList včetně řazení.
977    function renderCachedExhibitors($fieldDetailList, filters) {
978        var exhibitors = filterExhibitorsFromCache(filters);
979        $fieldDetailList.empty();
980        $fieldDetailList.data('exhibitors', exhibitors);
981        $fieldDetailList.data('isAscending', true);
982        $fieldDetailList.data('sortBy', 'name');
983        appendExhibitorListNew($fieldDetailList, exhibitors, true, 'name');
984
985        var $wrapper = $fieldDetailList.closest('.field-detail-wrapper');
986        $wrapper.find('.alphabet-sort').off('click.cached').on('click.cached', function () {
987            var asc = !$fieldDetailList.data('isAscending');
988            $fieldDetailList.data('isAscending', asc).data('sortBy', 'name');
989            updateSortIcons(asc, $(this));
990            appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'name');
991        });
992        $wrapper.find('.hall-sort').off('click.cached').on('click.cached', function () {
993            var asc = !$fieldDetailList.data('isAscending');
994            $fieldDetailList.data('isAscending', asc).data('sortBy', 'hala');
995            updateSortIcons(asc, $(this));
996            appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'hala');
997        });
998    }
999
1000    // ────────────────────────────────────────────────────────────────────────────
1001
1002    // Helper: render destination exhibitor list — používá cache, fallback na AJAX
1003    function fetchAndRenderDestinationExhibitors($fieldDetailList, filterData) {
1004        var cached = filterExhibitorsFromCache(filterData);
1005        if (cached !== null) {
1006            renderCachedExhibitors($fieldDetailList, filterData);
1007            return;
1008        }
1009
1010        // Fallback na AJAX (cache ještě není k dispozici — mělo by nastat jen při prvním načtení)
1011        $fieldDetailList.empty();
1012        $fieldDetailList.append('<div>Načítání...</div>');
1013        $.ajax({
1014            url: '/wp-json/api/v1/oborove-cleneni',
1015            method: 'POST',
1016            data: $.extend({ oboryLoadNeeded: false, catalog: currentCatalogSourceKey }, filterData),
1017            success: function (response) {
1018                var data = response['seznam-vystavovatelu'];
1019                $fieldDetailList.empty();
1020                $fieldDetailList.data('exhibitors', data).data('isAscending', true).data('sortBy', 'name');
1021                appendExhibitorListNew($fieldDetailList, data, true, 'name');
1022                var $w = $fieldDetailList.closest('.field-detail-wrapper');
1023                $w.find('.alphabet-sort').off('click.dest').on('click.dest', function () {
1024                    var asc = !$fieldDetailList.data('isAscending');
1025                    $fieldDetailList.data('isAscending', asc).data('sortBy', 'name');
1026                    updateSortIcons(asc, $(this));
1027                    appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'name');
1028                });
1029                $w.find('.hall-sort').off('click.dest').on('click.dest', function () {
1030                    var asc = !$fieldDetailList.data('isAscending');
1031                    $fieldDetailList.data('isAscending', asc).data('sortBy', 'hala');
1032                    updateSortIcons(asc, $(this));
1033                    appendExhibitorListNew($fieldDetailList, $fieldDetailList.data('exhibitors'), asc, 'hala');
1034                });
1035            },
1036            error: function () {
1037                $fieldDetailList.empty();
1038                $fieldDetailList.append('<div>Chyba při načítání vystavovatelů.</div>');
1039            }
1040        });
1041    }
1042
1043    // Helper: render a destination level detail view (continent level or sub-destination level)
1044    // backTo: 'list' | 'continent'
1045    function renderDestinationLevel($interactableWrapper, opts) {
1046        // opts: { title, continentName, code, filterParams, backTo, backContinent }
1047        $interactableWrapper.find('.field-detail-wrapper').remove();
1048
1049        const subDests = opts.code
1050            ? getSubDestinations(opts.continentName, opts.code)
1051            : (destinationsHierarchy && destinationsHierarchy[opts.continentName] ? destinationsHierarchy[opts.continentName] : []);
1052
1053        const subDestsHTML = buildSubDestsHTML(subDests, opts.continentName);
1054
1055        const backAttr = opts.backTo === 'continent'
1056            ? `data-back-to="continent" data-back-continent="${opts.backContinent}"`
1057            : '';
1058
1059        const sortButtonsHTML = `<div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div>`;
1060
1061        const fieldDetailHTML = `
1062            <div class="field-detail-wrapper" data-continent="${opts.continentName}" style="display: flex; flex-direction: column; gap: 16px;">
1063                <a href="#" class="back-katalog-button w-inline-block" ${backAttr}>
1064                    <div class="back-icon w-embed">
1065                        <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
1066                            <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
1067                        </svg>
1068                    </div>
1069                    <div>Zpět</div>
1070                </a>
1071                <h2 class="fields-headline">${opts.title}</h2>
1072                ${subDestsHTML}
1073                ${sortButtonsHTML}
1074                <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul>
1075            </div>
1076        `;
1077
1078        $interactableWrapper.append(fieldDetailHTML);
1079        fetchAndRenderDestinationExhibitors($interactableWrapper.find('.field-detail-exhibitor-list'), opts.filterParams);
1080    }
1081
1082    // Odkazy v detailu vystavovatele (obory / destinace) → filtrují katalog in-place bez reloadu.
1083    // (href zůstává funkční pro otevření v novém panelu, kde se filtr obnoví z URL.)
1084    $(document).on('click', '.catalog-filter-link', function (e) {
1085        // Nechat fungovat ctrl/cmd/middle-klik (otevření v nové kartě přes href)
1086        if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return;
1087        e.preventDefault();
1088
1089        var $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog');
1090        if ($interactableWrapper.length === 0) {
1091            $interactableWrapper = $(this).closest('.newsletter-card-wrapper').find('.interactable-part-wrapper-catalog');
1092        }
1093
1094        var type = $(this).data('filter-type');
1095
1096        if (type === 'obor') {
1097            var oborCode = String($(this).data('obor'));
1098            var $listFilter = activateKatalogTab($interactableWrapper, 'fields');
1099            var $panel = $listFilter.find('.js-katalog-panel[data-panel="fields"]');
1100            if ($panel.length > 0) {
1101                renderFieldsLevel($panel, oborCode);
1102                updateCatalogUrl({ tab: 'fields', obor: oborCode });
1103            }
1104        } else if (type === 'destination') {
1105            var destName = $(this).data('destination');
1106            var continent = $(this).data('continent');
1107            activateKatalogTab($interactableWrapper, 'destinations');
1108            // Po kliknutí na kontinent se díky pendingDestinationRestore zanoří až na destinaci
1109            pendingDestinationRestore = destName;
1110            $interactableWrapper.find('.catalog-filter-detail--box.continent-filter').filter(function () {
1111                return $(this).data('name') === continent;
1112            }).first().trigger('click');
1113        }
1114    });
1115
1116    // Handle continent filter clicks
1117    $(document).on('click', '.catalog-filter-detail--box.continent-filter', function () {
1118        const continentName = $(this).data('name');
1119        const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog');
1120
1121        updateCatalogUrl({ tab: 'destinations', continent: continentName });
1122
1123        $interactableWrapper.find('.list-with-filter').hide();
1124        $interactableWrapper.find('.company-detail-wrapper-new').hide();
1125
1126        renderDestinationLevel($interactableWrapper, {
1127            title: continentName,
1128            continentName: continentName,
1129            code: null,
1130            filterParams: { continents: [continentName] },
1131            backTo: 'list',
1132        });
1133
1134        // Pokud čekáme na obnovení konkrétní destinace z URL
1135        if (pendingDestinationRestore) {
1136            const pendingDest = pendingDestinationRestore;
1137            pendingDestinationRestore = null;
1138            setTimeout(function () {
1139                $interactableWrapper.find('.catalog-filter-detail--box.destination-filter').filter(function () {
1140                    return $(this).data('name') === pendingDest;
1141                }).first().trigger('click');
1142            }, 100);
1143        }
1144    });
1145
1146    // Handle destination filter clicks — in-place update existing field-detail-wrapper
1147    $(document).on('click', '.catalog-filter-detail--box.destination-filter', function () {
1148        const destinationName = $(this).data('name');
1149        const destinationCode = $(this).data('code') || '';
1150        const continentName   = $(this).data('continent') || '';
1151        const $fieldDetailWrapper = $(this).closest('.field-detail-wrapper');
1152
1153        updateCatalogUrl({ tab: 'destinations', continent: continentName, destination: destinationName });
1154
1155        const displayTitle = destinationCode ? destinationCode + ' ' + destinationName : destinationName;
1156
1157        // Aktualizovat nadpis
1158        $fieldDetailWrapper.find('h2.fields-headline').text(displayTitle);
1159
1160        // Aktualizovat back button — zpět na kontinent
1161        const $backBtn = $fieldDetailWrapper.find('.back-katalog-button');
1162        $backBtn.attr('data-back-to', 'continent');
1163        $backBtn.attr('data-back-continent', continentName);
1164
1165        // Nahradit sub-nomenklatury (pokud existují hlubší úrovně, jinak se nezobrazí nic)
1166        $fieldDetailWrapper.find('.destination-sub-boxes').remove();
1167        const subDests = getSubDestinations(continentName, destinationCode);
1168        if (subDests.length > 0) {
1169            const subDestsHTML = buildSubDestsHTML(subDests, continentName);
1170            $fieldDetailWrapper.find('h2.fields-headline').after(subDestsHTML);
1171        }
1172
1173        // Načíst vystavovatele pro tuto destinaci
1174        const $fieldDetailList = $fieldDetailWrapper.find('.field-detail-exhibitor-list');
1175        fetchAndRenderDestinationExhibitors($fieldDetailList, { destinations: [destinationName] });
1176    });
1177
1178    // Handle hall filter clicks (plan tab) - show exhibitors for a specific hall
1179    $(document).on('click', '.catalog-filter-detail--box.hall-filter', function () {
1180        var hallKey = $(this).data('hall').toString();
1181        var hallImage = $(this).data('image');
1182        var $panel = $(this).closest('.js-katalog-panel[data-panel="plan"]');
1183        var $planLayout = $panel.find('.plan-layout');
1184
1185        updateCatalogUrl({ tab: 'plan', hall: hallKey });
1186
1187        // Swap the map image to the hall plan (keep main plan if no hall image)
1188        if (hallImage) {
1189            $planLayout.find('.plan-map-image').attr('src', hallImage);
1190            ensurePlanMapLightbox($planLayout, hallImage, 'Plánek haly');
1191        }
1192
1193        // Hide the halls grid
1194        $planLayout.find('.plan-halls-grid').hide();
1195        $planLayout.find('.plan-hall-detail').remove();
1196
1197        // Filter exhibitors by hall from allExhibitorsData
1198        var filteredExhibitors = {};
1199        if (allExhibitorsData) {
1200            Object.keys(allExhibitorsData).forEach(function (key) {
1201                var item = allExhibitorsData[key];
1202                // Párуj na celou hodnotu haly ("1", "Vstupní hala I", "Venkovní plocha"),
1203                // ne jen na číslici — jinak by vstupní haly (římské číslice) vypadly.
1204                // Normalizace sjednotí "1" i "Hala 1" a zachová pojmenované plochy (římské číslice).
1205                if (normalizeHallKey(item.s_hala) === hallKey) {
1206                    filteredExhibitors[key] = item;
1207                }
1208            });
1209        }
1210
1211        // Store filtered data on the panel for search
1212        $panel.data('hall-exhibitors', filteredExhibitors);
1213
1214        // Insert top bar (back + search) BEFORE the plan-layout
1215        var topBarHTML =
1216            '<div class="plan-hall-topbar" style="display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;">' +
1217            '<a href="#" class="back-katalog-button plan-back-button w-inline-block">' +
1218            '<div class="back-icon w-embed">' +
1219            '<svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">' +
1220            '<path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>' +
1221            '</svg>' +
1222            '</div>' +
1223            '<div>Zpět</div>' +
1224            '</a>' +
1225            '<form class="search full-width-mobile w-form" onsubmit="return false;" style="margin:0;">' +
1226            '<input class="search-input w-input plan-hall-search-input" maxlength="256" placeholder="Hledat…" type="search" />' +
1227            '<div class="icon-24px search-input-icon w-embed">' +
1228            '<svg width="24" height="24" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">' +
1229            '<path d="M21 21L15 15M3 10C3 10.9193 3.18106 11.8295 3.53284 12.6788C3.88463 13.5281 4.40024 14.2997 5.05025 14.9497C5.70026 15.5998 6.47194 16.1154 7.32122 16.4672C8.17050 16.8189 9.08075 17 10 17C10.9193 17 11.8295 16.8189 12.6788 16.4672C13.5281 16.1154 14.2997 15.5998 14.9497 14.9497C15.5998 14.2997 16.1154 13.5281 16.4672 12.6788C16.8189 11.8295 17 10.9193 17 10C17 9.08075 16.8189 8.17050 16.4672 7.32122C16.1154 6.47194 15.5998 5.70026 14.9497 5.05025C14.2997 4.40024 13.5281 3.88463 12.6788 3.53284C11.8295 3.18106 10.9193 3 10 3C9.08075 3 8.17050 3.18106 7.32122 3.53284C6.47194 3.88463 5.70026 4.40024 5.05025 5.05025C4.40024 5.70026 3.88463 6.47194 3.53284 7.32122C3.18106 8.17050 3 9.08075 3 10Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>' +
1230            '</svg>' +
1231            '</div>' +
1232            '</form>' +
1233            '</div>';
1234
1235        $panel.before(topBarHTML);
1236
1237        var hallDetailHTML =
1238            '<div class="plan-hall-detail" style="flex:1 1 40%;min-width:250px;display:flex;flex-direction:column;gap:16px;">' +
1239            '<h2 class="fields-headline">VYSTAVOVATELÉ</h2>' +
1240            '<ul role="list" class="companies-list katalog plan-hall-exhibitor-list"></ul>' +
1241            '</div>';
1242
1243        $planLayout.append(hallDetailHTML);
1244
1245        var $fieldDetailList = $planLayout.find('.plan-hall-exhibitor-list');
1246
1247        if (Object.keys(filteredExhibitors).length > 0) {
1248            appendExhibitorListNew($fieldDetailList, filteredExhibitors, true, 'name');
1249        } else {
1250            $fieldDetailList.append('<div>V této hale nebyli nalezeni žádní vystavovatelé.</div>');
1251        }
1252
1253        syncPlanScrollablePanels($panel);
1254    });
1255
1256    // Search within hall exhibitors
1257    $(document).on('input', '.plan-hall-search-input', debounce(function () {
1258        var searchTerm = $(this).val().trim().toLowerCase();
1259        var $topbar = $(this).closest('.plan-hall-topbar');
1260        var $panel = $topbar.next('.js-katalog-panel[data-panel="plan"]');
1261        var allHallExhibitors = $panel.data('hall-exhibitors') || {};
1262        var $fieldDetailList = $panel.find('.plan-hall-exhibitor-list');
1263
1264        if (!searchTerm) {
1265            // No search term — show all hall exhibitors
1266            appendExhibitorListNew($fieldDetailList, allHallExhibitors, true, 'name');
1267            return;
1268        }
1269
1270        // Filter by name
1271        var filtered = {};
1272        Object.keys(allHallExhibitors).forEach(function (key) {
1273            var item = allHallExhibitors[key];
1274            var name = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' ').toLowerCase();
1275            if (name.indexOf(searchTerm) !== -1) {
1276                filtered[key] = item;
1277            }
1278        });
1279
1280        if (Object.keys(filtered).length > 0) {
1281            appendExhibitorListNew($fieldDetailList, filtered, true, 'name');
1282        } else {
1283            $fieldDetailList.empty();
1284            $fieldDetailList.append('<div>Vyhledávání neodpovídají žádní vystavující.</div>');
1285        }
1286    }, 300));
1287
1288    // Back button from hall exhibitor detail - return to hall list
1289    $(document).on('click', '.plan-back-button', function (e) {
1290        e.preventDefault();
1291        e.stopImmediatePropagation();
1292        var $topbar = $(this).closest('.plan-hall-topbar');
1293        var $panel = $topbar.next('.js-katalog-panel[data-panel="plan"]');
1294        var $planLayout = $panel.find('.plan-layout');
1295
1296        // Restore main plan image
1297        if (mainPlanImageUrl) {
1298            $planLayout.find('.plan-map-image').attr('src', mainPlanImageUrl);
1299            ensurePlanMapLightbox($planLayout, mainPlanImageUrl);
1300        }
1301
1302        // Remove hall detail, show halls grid, remove top bar
1303        $planLayout.find('.plan-hall-detail').remove();
1304        $planLayout.find('.plan-halls-grid').show();
1305        $topbar.remove();
1306
1307        updateCatalogUrl({ tab: 'plan' });
1308    });
1309
1310    // Handle parent category clicks - drill down in hierarchy
1311    $(document).on('click', '.catalog-filter-detail--box.category-parent', function () {
1312        var clickedCode = $(this).data('code');
1313        var $panel = $('.js-katalog-panel[data-panel="fields"]');
1314        if ($panel.length > 0) {
1315            renderFieldsLevel($panel, clickedCode);
1316            updateCatalogUrl({ tab: 'fields', obor: clickedCode });
1317        }
1318    });
1319
1320    // Handle back button within obory hierarchy
1321    $(document).on('click', '.obory-back-button', function (e) {
1322        e.preventDefault();
1323        var parentCode = $(this).data('parent');
1324        var $panel = $('.js-katalog-panel[data-panel="fields"]');
1325        if ($panel.length > 0) {
1326            renderFieldsLevel($panel, parentCode);
1327            updateCatalogUrl({ tab: 'fields', obor: parentCode || null });
1328        }
1329    });
1330
1331    // Handle category filter clicks - show field detail with exhibitor list
1332    $(document).on('click', '.catalog-filter-detail--box.category-filter', function () {
1333        const categoryName = $(this).data('name');
1334        const categoryCode = $(this).data('code');
1335        const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog');
1336
1337        updateCatalogUrl({ tab: 'fields', obor: categoryCode });
1338
1339        // Hide the list-with-filter (obory list)
1340        $interactableWrapper.find('.list-with-filter').hide();
1341        $interactableWrapper.find('.company-detail-wrapper-new').hide();
1342
1343        // Remove any existing field detail wrapper
1344        $interactableWrapper.find('.field-detail-wrapper').remove();
1345
1346        // Create field detail wrapper
1347        const fieldDetailHTML = `
1348            <div class="field-detail-wrapper" style="display: flex; flex-direction: column; gap: 16px;">
1349                <a href="#" class="back-katalog-button w-inline-block">
1350                                            <div class="back-icon w-embed">
1351                                                <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
1352                                                    <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
1353                                                </svg>
1354                                            </div>
1355                                            <div>Zpět</div>
1356                                        </a>               
1357                <h2 class="fields-headline">${categoryName}</h2>
1358                <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed">
1358<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div>
1359                <ul role="list" class="companies-list katalog field-detail-exhibitor-list"></ul>
1360            </div>
1361        `;
1362
1363        $interactableWrapper.append(fieldDetailHTML);
1364
1365        const $fieldDetailList = $interactableWrapper.find('.field-detail-exhibitor-list');
1366        renderCachedExhibitors($fieldDetailList, { categories: [categoryCode] });
1367    });
1368
1369    // Back button in field detail wrapper
1370    $(document).on('click', '.field-detail-wrapper .back-katalog-button', function (e) {
1371        e.preventDefault();
1372        e.stopImmediatePropagation();
1373        var $btn = $(this);
1374        var backTo = $btn.data('back-to');
1375        var $interactableWrapper = $btn.closest('.interactable-part-wrapper-catalog');
1376
1377        // Destination sub-level → back to continent level
1378        if (backTo === 'continent') {
1379            var backContinent = $btn.data('back-continent');
1380            updateCatalogUrl({ tab: 'destinations', continent: backContinent });
1381            renderDestinationLevel($interactableWrapper, {
1382                title: backContinent,
1383                continentName: backContinent,
1384                code: null,
1385                filterParams: { continents: [backContinent] },
1386                backTo: 'list',
1387            });
1388            return;
1389        }
1390
1391        // Default: return to main tab list
1392        $interactableWrapper.find('.field-detail-wrapper').remove();
1393        $interactableWrapper.find('.list-with-filter').css('display', '');
1394
1395        var activeTab = $('.js-katalog-tabs input[type="radio"]:checked').closest('label').data('tab') || 'companies';
1396        if (activeTab === 'fields') {
1397            var $fieldsPanel = $interactableWrapper.find('.js-katalog-panel[data-panel="fields"]');
1398            var currentObor = $fieldsPanel.data('current-obor');
1399            updateCatalogUrl({ tab: 'fields', obor: currentObor || null });
1400        } else if (activeTab === 'companies') {
1401            updateCatalogUrl({});
1402        } else {
1403            updateCatalogUrl({ tab: activeTab });
1404        }
1405    });
1406
1407    // Handle exhibitor click from field detail view
1408    $(document).on('click', '.company-detail-from-field', function (e) {
1409        e.preventDefault();
1410        const companyData = $(this).data('company');
1411        const $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog');
1412
1413        setExhibitorParam(companyData.name);
1414
1415        let $detail = $interactableWrapper.find('.company-detail-wrapper-new');
1416        if ($detail.length === 0) {
1417            $detail = $('.company-detail-wrapper-new');
1418        }
1419
1420        // Determine navigation source
1421        var isFromOboryDrilled = $(this).closest('.obory-exhibitors').length > 0;
1422        var isFromPlanHall = $(this).closest('.plan-hall-detail').length > 0;
1423
1424        if (isFromOboryDrilled) {
1425            $detail.data('navigation-source', 'obory-drilled');
1426            $interactableWrapper.find('.list-with-filter').hide();
1427        } else if (isFromPlanHall) {
1428            $detail.data('navigation-source', 'plan-hall');
1429            $interactableWrapper.find('.list-with-filter').hide();
1430            $interactableWrapper.find('.plan-hall-topbar').hide();
1431        } else {
1432            $detail.data('navigation-source', 'field-detail');
1433            $interactableWrapper.find('.field-detail-wrapper').hide();
1434        }
1435
1436        $detail.css({ display: 'flex', opacity: 1 });
1437        populateCompanyDetail(companyData, $detail);
1438    });
1439
1440    $(document).on('click', '.filter-reset', function () {
1441        // Uncheck all checkboxes
1442        $('input[type="checkbox"]').prop('checked', false);
1443        $('input[type="checkbox"]').siblings('.w-checkbox-input').removeClass('w--redirected-checked');
1444
1445        // Clear the selectedCategories array if you have it
1446        selectedCategories = [];
1447        selectedZnacky = [];
1448        selectedCountries = [];
1449        selectedDestinations = [];
1450
1451        // Remove active class from country boxes
1452        $('.catalog-filter-detail--box.country-filter').removeClass('is-active');
1453
1454        // Optionally re-fetch the exhibitors without any categories
1455        fetchExhibitors($('.search-input').val().trim(), selectedCategories, false, selectedZnacky, selectedCountries, selectedDestinations);
1456    });
1457
1458});
1459
1460function debounce(func, delay) {
1461    let timeout;
1462    return function (...args) {
1463        clearTimeout(timeout); // Clear the timeout if the function is called again within the delay
1464        timeout = setTimeout(() => {
1465            func.apply(this, args); // Call the function after the delay
1466        }, delay);
1467    };
1468}
1469
1470function appendCountries(data) {
1471    const $panel = $('.js-katalog-panel[data-panel="countries"]');
1472    if ($panel.length === 0) return;
1473
1474    $panel.empty();
1475
1476    if (!data || data.length === 0) {
1477        $panel.append('<div>Nebyly nalezeny žádné státy.</div>');
1478        return;
1479    }
1480
1481    data.forEach(function (countryName) {
1482        if (!countryName) return;
1483
1484        const boxHTML = `
1485            <div class="catalog-filter-detail--box country-filter" data-name="${countryName}" style="cursor: pointer;">
1486                <div class="img">
1487                     <img class="img" src="${customBlockData.pluginUrl}images/files.svg" />
1488                </div>
1489                <span class="list-item">${countryName}</span>
1490            </div>
1491        `;
1492
1493        $panel.append(boxHTML);
1494    });
1495}
1496
1497function appendBrandsPanel(data) {
1498    const $panel = $('.js-katalog-panel[data-panel="brands"]');
1499    if ($panel.length === 0) return;
1500
1501    $panel.empty();
1502
1503    if (!data || Object.keys(data).length === 0) {
1504        $panel.append('<div>Nebyly nalezeny žádné značky.</div>');
1505        return;
1506    }
1507
1508    // Sort brands alphabetically by name
1509    const sorted = Object.keys(data)
1510        .filter(function (key) { return key !== ''; })
1511        .map(function (key) { return data[key]; })
1512        .sort(function (a, b) {
1513            var nameA = (a.name || '').toLowerCase();
1514            var nameB = (b.name || '').toLowerCase();
1515            if (nameA < nameB) return -1;
1516            if (nameA > nameB) return 1;
1517            return 0;
1518        });
1519
1520    sorted.forEach(function (item) {
1521        var brandName = item.name || '';
1522        if (!brandName) return;
1523
1524        var boxHTML = `
1525            <div class="catalog-filter-detail--box brand-filter" data-name="${brandName}" style="cursor: pointer;">
1526                <div class="img">
1527                     <img class="img" src="${customBlockData.pluginUrl}images/files.svg" />
1528                </div>
1529                <span class="list-item">${brandName}</span>
1530            </div>
1531        `;
1532
1533        $panel.append(boxHTML);
1534    });
1535
1536    if ($panel.children().length === 0) {
1537        $panel.append('<div>Nebyly nalezeny žádné značky.</div>');
1538    }
1539}
1540
1541function appendDestinationsPanel(data) {
1542    const $panel = $('.js-katalog-panel[data-panel="destinations"]');
1543    if ($panel.length === 0) return;
1544
1545    $panel.empty();
1546
1547    if (!data || Object.keys(data).length === 0) {
1548        return;
1549    }
1550
1551    // Render continent boxes (hierarchy is already sorted alphabetically from PHP)
1552    Object.keys(data).forEach(function (continentName) {
1553        if (!continentName) return;
1554
1555        const boxHTML = `
1556            <div class="catalog-filter-detail--box continent-filter" data-name="${continentName}" style="cursor: pointer;">
1557                <div class="img">
1558                     <img class="img" src="${customBlockData.pluginUrl}images/files.svg" />
1559                </div>
1560                <span class="list-item">${continentName}</span>
1561            </div>
1562        `;
1563
1564        $panel.append(boxHTML);
1565    });
1566}
1567
1568function fetchPlanData() {
1569    var hallsDeferred = $.Deferred();
1570    var imageDeferred = $.Deferred();
1571
1572    // Fetch hall data
1573    $.ajax({
1574        url: '/wp-json/api/v1/exhibition-plan',
1575        method: 'POST',
1576        data: {
1577            hallLoadNeeded: true,
1578            catalog: currentCatalogSourceKey
1579        },
1580        success: function (response) {
1581            planHallsData = response['haly'] || null;
1582            hallsDeferred.resolve();
1583        },
1584        error: function () {
1585            console.log('Error fetching hall data.');
1586            hallsDeferred.resolve();
1587        }
1588    });
1589
1590    // Fetch main plan image
1591    $.ajax({
1592        url: '/wp-json/api/v1/exhibitor-plan-image',
1593        method: 'POST',
1594        data: {
1595            catalog: currentCatalogSourceKey
1596        },
1597        success: function (response) {
1598            var file = typeof response === 'string' ? JSON.parse(response) : response;
1599            if (file && file.id) {
1600                mainPlanImageUrl = 'https://is.abf.cz/file/raster?nl_id=' + file.id + '&s_format_to=png';
1601            }
1602            imageDeferred.resolve();
1603        },
1604        error: function () {
1605            console.log('Error fetching plan image.');
1606            imageDeferred.resolve();
1607        }
1608    });
1609
1610    // After both resolve, render the panel and show the tab
1611    $.when(hallsDeferred, imageDeferred).done(function () {
1612        if (planHallsData && Object.keys(planHallsData).length > 0) {
1613            appendPlanPanel();
1614            $('.js-katalog-tabs label[data-tab="plan"]').show();
1615        }
1616    });
1617}
1618
1619function appendPlanPanel() {
1620    var $panel = $('.js-katalog-panel[data-panel="plan"]');
1621    if ($panel.length === 0) return;
1622
1623    $panel.empty();
1624
1625    // Sort halls numerically
1626    var sortedHalls = Object.keys(planHallsData)
1627        .map(function (key) { return planHallsData[key]; })
1628        .sort(function (a, b) {
1629            // Číslované haly nejdřív (číselně), pojmenované plochy (bez čísla) na konec.
1630            var numA = (safeDecodeURIComponent(a.s_file_type).match(/\d+/) || [9999])[0];
1631            var numB = (safeDecodeURIComponent(b.s_file_type).match(/\d+/) || [9999])[0];
1632            return parseInt(numA, 10) - parseInt(numB, 10);
1633        });
1634
1635    // Build flex container: map (left) + hall grid (right)
1636    var containerHTML = '<div class="plan-layout" style="display:flex;gap:24px;flex-wrap:wrap;">';
1637
1638    // Left: map image
1639    containerHTML += '<div class="plan-map-wrapper" style="flex:1 1 55%;min-width:300px;">';
1640    if (mainPlanImageUrl) {
1641        containerHTML += '<a class="plan-map-lightbox" href="' + mainPlanImageUrl + '" data-lightbox="catalog-plan-map" data-title="Plánek výstaviště" aria-label="Otevřít plánek ve větším náhledu" style="display:block;">';
1642        containerHTML += '<img class="plan-map-image" src="' + mainPlanImageUrl + '" style="width:100%;height:auto;cursor:zoom-in;" />';
1643        containerHTML += '</a>';
1644    }
1645    containerHTML += '</div>';
1646
1647    // Right: hall grid (scrollable, height synced to map image)
1648    containerHTML += '<div class="plan-halls-grid" style="flex:1 1 40%;min-width:250px;display:flex;flex-direction:column;gap:8px;overflow-y:auto;">';
1649
1650    sortedHalls.forEach(function (hall) {
1651        // s_file_type je už zobrazovaný název ("Hala 1", "Vstupní hala I", "Venkovní plocha").
1652        var displayName = safeDecodeURIComponent(hall.s_file_type).replace(/\+/g, ' ');
1653        // data-hall drží normalizovaný klíč haly pro párování ("1", "Vstupní hala I").
1654        var hallKey = normalizeHallKey(hall.s_hala != null ? hall.s_hala : hall.s_file_type);
1655        var hallImage = hall.s_file_url_jpg || '';
1656
1657        containerHTML += '<div class="catalog-filter-detail--box hall-filter" data-hall="' + hallKey.replace(/"/g, '&quot;') + '" data-image="' + hallImage + '" style="cursor:pointer; width: 100%;">';
1658        containerHTML += '<div class="img"><img class="img" src="' + customBlockData.pluginUrl + 'images/files.svg" /></div>';
1659        containerHTML += '<span class="list-item">' + displayName + '</span>';
1660        containerHTML += '</div>';
1661    });
1662
1663    containerHTML += '</div></div>';
1664
1665    $panel.append(containerHTML);
1666
1667    var $mapImg = $panel.find('.plan-map-image');
1668    ensurePlanMapLightbox($panel, mainPlanImageUrl);
1669    function updatePlanLayout() {
1670        syncPlanScrollablePanels($panel);
1671    }
1672    $mapImg.on('load', updatePlanLayout);
1673    // Also sync if image is already cached
1674    if ($mapImg[0] && $mapImg[0].complete) {
1675        updatePlanLayout();
1676    }
1677    $(window).off('resize.planScrollablePanels').on('resize.planScrollablePanels', debounce(function () {
1678        updatePlanLayout();
1679    }, 100));
1680
1681    // Check for pending hall restore from URL
1682    if (pendingHallRestore) {
1683        var hallToRestore = pendingHallRestore;
1684        pendingHallRestore = null;
1685        $('.catalog-filter-detail--box.hall-filter').filter(function () {
1686            return $(this).data('hall').toString() === hallToRestore.toString();
1687        }).first().trigger('click');
1688    }
1689}
1690
1691function updateSortIcons(isAscending, $sortButtonSelector) {
1692    // Update sort button icon logic based on the passed sortButtonSelector
1693
1694    $sortButtonSelector.addClass('is-active').siblings().removeClass('is-active');
1695    $sortButtonSelector.siblings().find('.sort-icon').removeClass('is-active');
1696
1697    if (isAscending) {
1698        $sortButtonSelector.find('.sort-icon').first().addClass('is-active'); // Show ascending icon
1699        $sortButtonSelector.find('.sort-icon').last().removeClass('is-active'); // Show ascending icon
1700
1701
1702    } else {
1703        $sortButtonSelector.find('.sort-icon').first().removeClass('is-active'); // Show ascending icon
1704        $sortButtonSelector.find('.sort-icon').last().addClass('is-active'); // Show ascending icon
1705    }
1706}
1707
1708function appendHallFilter(filtersWrapper, data) {
1709    const sortedData = Object.keys(data)
1710        .map(key => data[key]) // Convert the object into an array of its values
1711        .sort((a, b) => {
1712            // Decode the names (since they are URL-encoded)
1713            const decodedHallA = safeDecodeURIComponent(a.s_file_type).replace(/\+/g, ' ');
1714            const hallNumberA = decodedHallA.match(/\d+/g);
1715            const hallResultA = hallNumberA ? parseInt(hallNumberA[0], 10) : null;
1716
1717            const decodedHallB = safeDecodeURIComponent(b.s_file_type).replace(/\+/g, ' ');
1718            const hallNumberB = decodedHallB.match(/\d+/g);
1719            const hallResultB = hallNumberB ? parseInt(hallNumberB[0], 10) : null;
1720
1721            // If either value is null, treat it as the lowest priority
1722            if (hallResultA === null) return 1;
1723            if (hallResultB === null) return -1;
1724
1725            // Compare numerically
1726            return hallResultA - hallResultB;
1727        });
1728
1729    sortedData.unshift({ s_file_type: 'vše' });
1730
1731    // Clear the wrapper before adding new content
1732    filtersWrapper.empty();
1733
1734    filtersWrapper.append('<div class="filter-category-text">Hala:</div>');
1735
1736    // Loop through the data and create the labels dynamically
1737    Object.keys(sortedData).forEach(function (key) {
1738        const item = sortedData[key];
1739
1740        // Decode the name (since it's URL-encoded)
1741        const decodedHall = safeDecodeURIComponent(item.s_file_type).replace(/\+/g, ' ');
1742        const hallNumber = decodedHall.match(/\d+/g);
1743        const hallResult = hallNumber ? hallNumber[0] : decodedHall;
1744
1745        const labelHTML = `                  
1746                    <label class="filter-button w-radio ${hallResult === "vše" ? 'is-active' : ''}">
1747                        <div class="w-form-formradioinput w-form-formradioinput--inputType-custom radio_button w-radio-input"></div>
1748                        <input class="hall-filter-input" data-name="dispozice" id="${item.nl_id}" name="dispozice" style="opacity:0;position:absolute;z-index:-1" type="radio" value="${hallResult}">
1749                        <span class="radio_label w-form-label hall-filter-span" for="${item.nl_id}" fs-cmsfilter-active="is-active" fs-cmsfilter-field="dispozice">
1750                            ${hallResult}
1751                        </span>
1752                    </label>
1753                `;
1754
1755        // Append to the list and assign data using .data()
1756        $(labelHTML).appendTo(filtersWrapper);
1757    });
1758}
1759
1760function buildLogoSrc(item) {
1761    // Priorita 1: s_file_url
1762    var fileUrl = safeDecodeURIComponent(item.s_file_url || '').replace(/(?!^)\+/g, ' ');
1763    if (fileUrl) return fileUrl;
1764
1765    // Priorita 2: s_logo_base64
1766    var base64 = item.s_logo_base64 || '';
1767    if (base64) {
1768        var mimeType = base64.startsWith('/9j/') ? 'image/jpeg' : 'image/png';
1769        return 'data:' + mimeType + ';base64,' + base64;
1770    }
1771
1772    return '';
1773}
1774
1775function appendLogosToWrapper(logos) {
1776    // Define the function to render the HTML structure for each logo
1777    function renderLogoHTML(logo, index) {
1778        // Create the anchor element with required attributes
1779        const anchor = document.createElement('a');
1780        anchor.setAttribute('key', index);
1781        anchor.className = 'logo-wrapper w-inline-block';
1782        anchor.target = '_blank';
1783        anchor.rel = 'noopener';
1784        anchor.href = safeDecodeURIComponent(logo.s_partner_url);
1785
1786        // Create the image element inside the anchor
1787        const img = document.createElement('img');
1788        img.alt = '';
1789        img.className = 'partner-logo-image';
1790        img.loading = 'lazy';
1791        img.src = logo.logo_image;
1792
1793        // Append the image to the anchor
1794        anchor.appendChild(img);
1795
1796        return anchor;
1797    }
1798
1799    // Function to append logos to a specific section
1800    function appendLogosToSection(logosArray, sectionClass) {
1801        const sectionWrapper = document.querySelector(`.${sectionClass} .logos-wrapper`);
1802        if (sectionWrapper) {
1803            logosArray.forEach((logo, index) => {
1804                const logoElement = renderLogoHTML(logo, index);
1805                sectionWrapper.appendChild(logoElement);
1806            });
1807        }
1808    }
1809
1810    // Append logos to each corresponding section
1811    if (logos.zastita.length > 0) {
1812        appendLogosToSection(logos.zastita, 'zastita');
1813    }
1814
1815    if (logos.media.length > 0) {
1816        appendLogosToSection(logos.media, 'media');
1817    }
1818
1819    if (logos.dalsi.length > 0) {
1820        appendLogosToSection(logos.dalsi, 'dalsi');
1821    }
1822}
1823
1824function appendExhibitorPlan(planListWrapper, data, haly) {
1825    const sortedData = Object.keys(data)
1826        .map(key => data[key]) // Convert the object into an array of its values
1827        .sort((a, b) => {
1828            let valA, valB;
1829            valA = safeDecodeURIComponent(a.s_nazev).toLowerCase();
1830            valB = safeDecodeURIComponent(b.s_nazev).toLowerCase();
1831
1832            if (valA < valB) return -1;
1833            if (valA > valB) return 1;
1834            return 0;
1835        });
1836
1837    const sortedDataHaly = Object.keys(haly)
1838        .map(key => haly[key]) // Convert the object into an array of its values
1839        .sort((a, b) => {
1840            // Decode the names (since they are URL-encoded)
1841            const decodedHallA = safeDecodeURIComponent(a.s_file_type).replace(/\+/g, ' ');
1842            const hallNumberA = decodedHallA.match(/\d+/g);
1843            const hallResultA = hallNumberA ? parseInt(hallNumberA[0], 10) : null;
1844
1845            const decodedHallB = safeDecodeURIComponent(b.s_file_type).replace(/\+/g, ' ');
1846            const hallNumberB = decodedHallB.match(/\d+/g);
1847            const hallResultB = hallNumberB ? parseInt(hallNumberB[0], 10) : null;
1848
1849            // If either value is null, treat it as the lowest priority
1850            if (hallResultA === null) return 1;
1851            if (hallResultB === null) return -1;
1852
1853            // Compare numerically
1854            return hallResultA - hallResultB;
1855        });
1856
1857
1858    planListWrapper.empty();
1859
1860    sortedData.forEach(function (item, index) {
1861
1862        let fileUrlJpg = '';
1863        Object.keys(sortedDataHaly).forEach(function (key) {
1864            const hallItem = sortedDataHaly[key];
1865
1866            // Decode the name (since it's URL-encoded)
1867            const decodedHall = safeDecodeURIComponent(hallItem.s_file_type).replace(/\+/g, ' ');
1868            const hallNumber = decodedHall.match(/\d+/g);
1869            const hallResult = hallNumber ? hallNumber[0] : decodedHall;
1870
1871
1872            if (hallResult == item.s_hala && hallItem.s_file_url_jpg) {
1873                fileUrlJpg = safeDecodeURIComponent(hallItem.s_file_url_jpg).replace(/(?!^)\+/g, ' ');
1874            }
1875        });
1876
1877
1878        // Decode the name (since it's URL-encoded)
1879        const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' ');
1880        const decodedHala = safeDecodeURIComponent(item.s_hala);
1881        const decodedStanek = safeDecodeURIComponent(item.s_stanek);
1882        const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' ');
1883        const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' ');
1884        const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' ');
1885        let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim();
1886
1887        let halaStanek = '';
1888        if (decodedHala !== '++++++') {
1889            halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`;
1890        } else if (decodedStanek !== '') {
1891            halaStanek = `--${decodedStanek}`;
1892        } else {
1893            halaStanek = '';
1894        }
1895
1896        let companyData = {
1897            halaStanek: halaStanek,
1898            name: decodedName,
1899            description: safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' '),
1900            address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip,
1901            email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(),
1902            phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' '),
1903            www: website,
1904            categories: [],
1905            fileUrl: buildLogoSrc(item),
1906            planUrlJpg: fileUrlJpg
1907        }
1908
1909        const listItemClass = index > 4 ? 'companies-list-item companies-list-item-js see-more-hidden' : 'companies-list-item';
1910
1911        // Create the label structure
1912        const labelHTML = `
1913            <li class="${listItemClass}">
1914                <a class="company-detail-link w-inline-block" href="#">
1915                    <div class="company-name">${decodedName}</div>
1916                    <div class="company-location">${halaStanek}</div>
1917                </a>
1918            </li>
1919        `;
1920
1921        // Append to the list and assign data using .data()
1922        const $label = $(labelHTML).appendTo(planListWrapper);
1923        $label.find('.company-detail-link').data('company', companyData);
1924    });
1925
1926    getDetailData($('.company-detail-link'))
1927
1928    Webflow.require('ix2').init();
1929
1930    $('.see-more').off('click').on('click', function () {
1931        const $this = $(this);
1932        const isExpanded = $this.data('expanded') || false;
1933
1934        $('.companies-list-item-js').toggleClass('see-more-hidden', isExpanded);
1935
1936        $this.text(isExpanded ? 'Zobrazit více' : 'Zobrazit méně');
1937        $this.data('expanded', !isExpanded);
1938    });
1939}
1940
1941function slugify(str) {
1942    str = str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); // normalize and remove diacritics
1943    str = str.replace(/^\s+|\s+$/g, ''); // trim leading/trailing white space
1944    str = str.toLowerCase(); // convert string to lowercase
1945    str = str.replace(/[^a-z0-9 -]/g, '') // remove any non-alphanumeric characters
1946        .replace(/\s+/g, '-') // replace spaces with hyphens
1947        .replace(/-+/g, '-'); // remove consecutive hyphens
1948    return str;
1949}
1950
1951function appendExhibitorList(exhibitorListWrapper, data, isAscending, sortBy) {
1952    const sortedData = Object.keys(data)
1953        .map(key => data[key]) // Convert the object into an array of its values
1954        .sort((a, b) => {
1955            let valA, valB;
1956
1957            if (sortBy === 'name') {
1958                // Sort by `s_nazev` (name)
1959                valA = safeDecodeURIComponent(a.s_nazev).toLowerCase();
1960                valB = safeDecodeURIComponent(b.s_nazev).toLowerCase();
1961            } else if (sortBy === 'hala') {
1962                // Sort by `s_hala` (hall)
1963                valA = safeDecodeURIComponent(a.s_hala).toLowerCase();
1964                valB = safeDecodeURIComponent(b.s_hala).toLowerCase();
1965            }
1966
1967            if (valA < valB) return isAscending ? -1 : 1;
1968            if (valA > valB) return isAscending ? 1 : -1;
1969            return 0;
1970        });
1971
1972    exhibitorListWrapper.empty();
1973
1974    if (sortedData.length > 0) {
1975        sortedData.forEach(function (item) {
1976            // Decode the name (since it's URL-encoded)
1977            const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' ');
1978            const decodedHala = safeDecodeURIComponent(item.s_hala);
1979            const decodedStanek = safeDecodeURIComponent(item.s_stanek);
1980            const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' ');
1981            const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' ');
1982            const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' ');
1983            let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim();
1984            let znackyArray = item.znacky_names;
1985            if (!website.startsWith("http://") && !website.startsWith("https://") && website !== '') {
1986                website = "https://" + website; // If not, add "https://" before the URL
1987            }
1988
1989            let halaStanek = '';
1990            if (decodedHala !== '++++++') {
1991                halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`;
1992            } else if (decodedStanek !== '') {
1993                halaStanek = `--${decodedStanek}`;
1994            } else {
1995                halaStanek = '';
1996            }
1997
1998
1999            let companyData = {
2000                halaStanek: halaStanek,
2001                name: decodedName,
2002                description: safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' '),
2003                address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip,
2004                email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(),
2005                phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' ').replace(/\//g, ''),
2006                www: website,
2007                categories: item.category_names,
2008                category_codes: item.category_codes,
2009                fileUrl: buildLogoSrc(item),
2010                znacky_names: znackyArray,
2011                destinace_names: item.destinace_names
2012            }
2013
2014
2015            // Create the label structure
2016            const labelHTML = `
2017                    <li class="companies-list-item">
2018                        <a href="#" data-company="${companyData}" class="company-detail-katalog-link w-inline-block">
2019                            <div class="company-name katalog">${decodedName}</div>
2020                            <div class="company-location katalog">${halaStanek}</div>
2021                        </a>
2022                    </li>
2023                `;
2024
2025            // Append to the list and assign data using .data()
2026            const $label = $(labelHTML).appendTo(exhibitorListWrapper);
2027            $label.find('.company-detail-katalog-link').data('company', companyData);
2028        });
2029
2030        getDetailData($('.company-detail-katalog-link'));
2031    } else {
2032        const labelHTML = `
2033                    <li class="companies-list-item">
2034                        <div class="company-name katalog">Vyhledávání neodpovídají žádní vystavující</div>                       
2035                    </li>
2036                `;
2037
2038        // Append to the list and assign data using .data()
2039        const $label = $(labelHTML).appendTo(exhibitorListWrapper);
2040        $label.find('.company-detail-katalog-link').data('company', null);
2041    }
2042
2043
2044    Webflow.require('ix2').init();
2045}
2046
2047function getDetailData($link) {
2048    $link.on('click', function (e) {
2049        const companyData = $(this).data('company');
2050
2051        $('#company-detail-title').text(companyData.name);
2052
2053        if (companyData.description === '') {
2054            $('#company-detail-description').hide();
2055        } else {
2056            $('#company-detail-description').text(companyData.description).show();
2057        }
2058
2059        if (companyData.halaStanek === '') {
2060            $('#company-detail-hall').hide();
2061        } else {
2062            $('#company-detail-hall .company-info-list-text').text(companyData.halaStanek);
2063            $('#company-detail-hall').show();
2064        }
2065
2066        $('#company-detail-address .company-info-list-text').text(companyData.address);
2067
2068        if (companyData.email === '' || companyData.email === '+') {
2069            $('#company-detail-email').hide();
2070        } else {
2071            $('#company-detail-email .company-info-list-text').attr('href', 'mailto:' + companyData.email).text(companyData.email);
2072            $('#company-detail-email').show();
2073        }
2074
2075        if (companyData.phone === '' || companyData.phone === '+') {
2076            $('#company-detail-phone').hide();
2077        } else {
2078            $('#company-detail-phone .company-info-list-text').attr('href', 'tel:' + companyData.phone.replace(/\//g, ''),).text(companyData.phone.replace(/\//g, ''),);
2079            $('#company-detail-phone').show();
2080        }
2081
2082        if (companyData.www === '') {
2083            $('#company-detail-www').hide();
2084        } else {
2085            $('#company-detail-www').attr('href', companyData.www).text(companyData.www).show();
2086        }
2087
2088        if (companyData.fileUrl === '') {
2089            $('.plan-logo-image-wrapper').hide();
2090        } else {
2091            $('.plan-logo-image').attr('src', companyData.fileUrl);
2092            $('.plan-logo-image-wrapper').show();
2093        }
2094
2095        if (companyData.planUrlJpg) {
2096            $('.hala-detail-plan-image').attr('src', companyData.planUrlJpg);
2097        } else {
2098            // Hala nemá vlastní obrázek – zobraz hlavní plán
2099            $('.hala-detail-plan-image').attr('src', $('.main-plan-image').attr('src'));
2100        }
2101
2102        if (companyData.categories.length > 0) {
2103            $('#company-detail-obory .company-detail-obory-list').empty();
2104            let hasValidCategory = false;
2105            companyData.categories.forEach(function (category) {
2106                if (category === '') {
2107                    return;
2108                }
2109
2110                hasValidCategory = true;
2111                let categoryName = safeDecodeURIComponent(category).replace(/\+/g, ' ');
2112
2113                $('#company-detail-obory .company-detail-obory-list').append(`
2114                    <li class="field-list-item">
2115                        <div class="dot-wrapper">
2116                            <div class="dot-icon"></div>
2117                        </div>
2118                        <div>${categoryName}</div>
2119                    </li>
2120                `);
2121            });
2122            if (hasValidCategory) {
2123                $('#company-detail-obory').show();
2124            } else {
2125                $('#company-detail-obory').hide();
2126            }
2127        } else {
2128            $('#company-detail-obory').hide();
2129        }
2130
2131        if (companyData.znacky_names.length > 0) {
2132            $('#company-detail-znacky .company-detail-znacky-list').empty();
2133            companyData.znacky_names.forEach(function (znacka) {
2134                $('#company-detail-znacky .company-detail-znacky-list').append(`
2135                    <li class="field-list-item">
2136                        <div class="dot-wrapper">
2137                            <div class="dot-icon"></div>
2138                        </div>
2139                        <div>${znacka}</div>
2140                    </li>
2141                `);
2142            });
2143            $('#company-detail-znacky').show();
2144        } else {
2145            $('#company-detail-znacky').hide();
2146        }
2147    });
2148}
2149
2150
2151function appendOboroveCleneni(filtersWrapper, data) {
2152    // Clear the wrapper before adding new content
2153    filtersWrapper.empty();
2154
2155    filtersWrapper.append('<div className="filter-body-spacer"></div>');
2156
2157    let hasValidFilter = false;
2158
2159    // Loop through the data and create the labels dynamically
2160    Object.keys(data).forEach(function (key) {
2161        if (key === "") {
2162            return;
2163        }
2164
2165        hasValidFilter = true;
2166        const item = data[key];
2167
2168        // Decode the name (since it's URL-encoded)
2169        const decodedName = safeDecodeURIComponent(item.s_name).replace(/\+/g, ' ');
2170        const decodedCode = safeDecodeURIComponent(item.s_code);
2171
2172        // Create a unique ID for each checkbox
2173        const checkboxId = `checkbox-${item.id}`;
2174
2175        // Create the label structure
2176        const labelHTML = `
2177                    <label class="w-checkbox checkbox-field">
2178                        <div class="w-checkbox-input w-checkbox-input--inputType-custom checkbox"></div>
2179                        <input type="checkbox" name="${checkboxId}" id="${checkboxId}" data-name="${decodedCode}" data-slug="${slugify(decodedName)}" style="opacity:0;position:absolute;z-index:-1"/>
2180                        <span class="checkbox-label w-form-label" for="${checkboxId}">${decodedName}</span>
2181                    </label>
2182                `;
2183
2184        // Append the created label to the wrapper
2185        filtersWrapper.append(labelHTML);
2186    });
2187
2188    if (!hasValidFilter) {
2189        filtersWrapper.append('<div>Nebyly nalezeny žádné obory.</div><div className="filter-body-spacer"></div>');
2190    }
2191}
2192
2193function appendZnacky(filtersWrapper, data) {
2194    // Clear the wrapper before adding new content
2195    filtersWrapper.empty();
2196
2197    filtersWrapper.append('<div className="filter-body-spacer"></div>');
2198
2199    let hasValidFilter = false;
2200
2201    // Loop through the data and create the labels dynamically
2202    Object.keys(data).forEach(function (key) {
2203        const item = data[key];
2204
2205        if (key === "") {
2206            return;
2207        }
2208
2209        hasValidFilter = true;
2210
2211        // Decode the name (since it's URL-encoded)
2212        const decodedName = item.name;
2213        const decodedCode = safeDecodeURIComponent(item.id);
2214
2215        // Create a unique ID for each checkbox
2216        const checkboxId = `checkbox-${item.id}`;
2217
2218        // Create the label structure
2219        const labelHTML = `
2220                    <label class="w-checkbox checkbox-field">
2221                        <div class="w-checkbox-input w-checkbox-input--inputType-custom checkbox"></div>
2222                        <input class="znacky-input-checkbox" type="checkbox" name="${checkboxId}" id="${checkboxId}" data-id="${decodedCode}" data-name="${decodedName}" data-slug="${slugify(decodedName)}" style="opacity:0;position:absolute;z-index:-1"/>
2223                        <span class="checkbox-label w-form-label" for="${checkboxId}">${decodedName}</span>
2224                    </label>
2225                `;
2226
2227        // Append the created label to the wrapper
2228        filtersWrapper.append(labelHTML);
2229    });
2230
2231    if (!hasValidFilter) {
2232        filtersWrapper.append('<div>Nebyly nalezeny žádné značky.</div><div className="filter-body-spacer"></div>');
2233    }
2234}
2235
2236// Newsletter form
2237function newsletterFormSubmit() {
2238    const form = $('.newsletter-form-wrapper');
2239    const visibleNewsletterButton = $('.button.newsletter:visible');
2240
2241    if (form.length > 0) {
2242        form.on('submit', function (event) {
2243            event.preventDefault(); // Prevent the default form submission behavior
2244
2245            // Update button text and hide messages
2246            visibleNewsletterButton.val(visibleNewsletterButton.data('wait'));
2247
2248            var formData = $(this).serialize();
2249
2250            var dataArray = $(this).serializeArray();
2251            var email = '';
2252
2253            $.ajax({
2254                url: '/wp-json/api/v1/newsletter', method: 'POST', data: formData, success: function (data) {
2255                    let response = '';
2256                    let isJson = true;
2257
2258                    if (typeof data === 'string') {
2259                        try {
2260                            response = JSON.parse(data);
2261                        } catch (e) {
2262                            isJson = false;
2263                            response = data;
2264                        }
2265                    }
2266                    if (isJson && response.res === 1) {
2267                        dataArray.forEach(function (field) {
2268                            if (field.name === 's_email') {
2269                                email = field.value;
2270                            }
2271                        });
2272
2273                        dataLayer.push({
2274                            "event": "newsletter_subscription", "email": email
2275                        });
2276
2277                        newsletterHandleSuccess(form);
2278                    } else {
2279                        newsletterHandleError();
2280                    }
2281                }, error: function () {
2282                    newsletterHandleError();
2283                }, complete: function () {
2284                    visibleNewsletterButton.val(visibleNewsletterButton.data('done'));
2285                }
2286            })
2287        });
2288    }
2289}
2290
2291function newsletterHandleSuccess(form) {
2292    const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox');
2293    const agreementInput = form.find('#b_agree_gdpr');
2294    const successMessage = $('.w-form-done');
2295    const errorMessage = $('.w-form-fail');
2296
2297    successMessage.fadeIn();
2298    errorMessage.fadeOut();
2299
2300    agreementInput.prop('checked', false);
2301    agreementCustomCheck.removeClass('w--redirected-checked');
2302    $(form)[0].reset();
2303
2304    setTimeout(function () {
2305        successMessage.fadeOut();
2306    }, 5000);
2307}
2308
2309function newsletterHandleError(errorMsg = '') {
2310    const successMessage = $('.w-form-done');
2311    const errorMessage = $('.w-form-fail');
2312
2313    if (errorMsg !== '') {
2314        errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>');
2315    }
2316
2317    successMessage.fadeOut();
2318    errorMessage.fadeIn();
2319
2320    setTimeout(function () {
2321        errorMessage.fadeOut();
2322    }, 5000);
2323}
2324
2325// Vystavovatel form
2326function exhibitorFormSubmit() {
2327    const form = $('#exhibitor-registration-form');
2328    const submitButton = form.find('.button');
2329
2330    form.on('submit', function (e) {
2331        e.preventDefault(); // Prevent the form from submitting the traditional way
2332
2333        submitButton.val(submitButton.data('wait'));
2334
2335        // Gather the form data
2336        var formData = $(this).serialize();
2337
2338        var dataArray = $(this).serializeArray();
2339        var email = '';
2340        var telefon = '';
2341
2342        if (window.iti.isValidNumberPrecise()) {
2343            // Make the API call
2344            $.ajax({
2345                url: '/wp-json/api/v1/exhibitor-registration',
2346                method: 'POST',
2347                data: formData,
2348                success: function (data) {
2349                    let response = '';
2350                    let isJson = true;
2351
2352                    if (typeof data === 'string') {
2353                        try {
2354                            response = JSON.parse(data);
2355                        } catch (e) {
2356                            isJson = false;
2357                            response = data;
2358                        }
2359                    }
2360                    if (isJson && response.res === 1) {
2361                        dataArray.forEach(function (field) {
2362                            if (field.name === 's_email') {
2363                                email = field.value;
2364                            } else if (field.name === 's_phone') {
2365                                if (window.iti) {
2366                                    telefon = window.iti.getNumber(); // Get the full phone number with country code
2367                                } else {
2368                                    telefon = field.value;
2369                                }
2370                            }
2371                        });
2372                        if (window.iti) {
2373                            telefon = window.iti.getNumber();
2374                        }
2375                        dataLayer.push({
2376                            "event": "exhibitor_registration_sent",
2377                            "email": email,
2378                            "phone": telefon
2379                        });
2380
2381
2382                        exhibitorHandleSuccess(form);
2383                    } else {
2384                        exhibitorHandleError();
2385                    }
2386                },
2387                error: function () {
2388                    exhibitorHandleError();
2389                },
2390                complete: function () {
2391                    submitButton.val(submitButton.data('done'));
2392                }
2393            })
2394        } else {
2395            // If the phone number is not valid, show an error message
2396            exhibitorHandleError('Zadejte platné telefonní číslo.');
2397            submitButton.val(submitButton.data('done'));
2398        }
2399    });
2400}
2401
2402function exhibitorHandleSuccess(form) {
2403    const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox');
2404    const agreementInput = form.find('#b_agree_vop');
2405    const successMessage = $('.w-form-done');
2406    const errorMessage = $('.w-form-fail');
2407
2408    successMessage.fadeIn();
2409    errorMessage.fadeOut();
2410
2411    agreementInput.prop('checked', false);
2412    agreementCustomCheck.removeClass('w--redirected-checked');
2413    $(form)[0].reset();
2414
2415    setTimeout(function () {
2416        successMessage.fadeOut();
2417    }, 5000);
2418}
2419
2420function exhibitorHandleError(errorMsg = '') {
2421    const successMessage = $('.w-form-done');
2422    const errorMessage = $('.w-form-fail');
2423
2424    if (errorMsg !== '') {
2425        errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>');
2426    }
2427
2428    successMessage.fadeOut();
2429    errorMessage.fadeIn();
2430
2431    setTimeout(function () {
2432        errorMessage.fadeOut();
2433    }, 5000);
2434}
2435
2436// Kontakt form
2437function contactFormSubmit() {
2438    const form = $('#contact-form');
2439    const submitButton = form.find('.button');
2440
2441    form.on('submit', function (e) {
2442        e.preventDefault(); // Prevent the form from submitting the traditional way
2443
2444        submitButton.val(submitButton.data('wait'));
2445
2446        // Gather the form data
2447        var formData = $(this).serialize();
2448
2449        var dataArray = $(this).serializeArray();
2450        var email = '';
2451        var telefon = '';
2452
2453
2454        if (window.iti.isValidNumberPrecise()) {
2455            // Make the API call
2456            $.ajax({
2457                url: '/wp-json/api/v1/contact',
2458                method: 'POST',
2459                data: formData,
2460                success: function (data) {
2461                    let response = '';
2462                    let isJson = true;
2463
2464                    if (typeof data === 'string') {
2465                        try {
2466                            response = JSON.parse(data);
2467                        } catch (e) {
2468                            isJson = false;
2469                            response = data;
2470                        }
2471                    }
2472                    if (isJson && response.res === 1) {
2473                        dataArray.forEach(function (field) {
2474                            if (field.name === 's_email') {
2475                                email = field.value;
2476                            } else if (field.name === 's_phone') {
2477                                telefon = field.value;
2478                            }
2479                        });
2480                        if (window.iti) {
2481                            telefon = window.iti.getNumber();
2482                        }
2483                        dataLayer.push({
2484                            "event": "contact_form_sent",
2485                            "email": email,
2486                            "phone": telefon
2487                        });
2488
2489                        contactHandleSuccess(form);
2490                    } else {
2491                        contactHandleError();
2492                    }
2493                },
2494                error: function () {
2495                    contactHandleError();
2496                },
2497                complete: function () {
2498                    submitButton.val(submitButton.data('done'));
2499                }
2500            })
2501        } else {
2502            // If the phone number is not valid, show an error message
2503            contactHandleError('Zadejte platné telefonní číslo.');
2504            submitButton.val(submitButton.data('done'));
2505        }
2506    });
2507}
2508
2509function contactHandleSuccess(form) {
2510    const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox');
2511    const agreementInput = form.find('#b_agree_vop');
2512    const successMessage = $('.w-form-done');
2513    const errorMessage = $('.w-form-fail');
2514
2515    successMessage.fadeIn();
2516    errorMessage.fadeOut();
2517
2518    agreementInput.prop('checked', false);
2519    agreementCustomCheck.removeClass('w--redirected-checked');
2520    $(form)[0].reset();
2521
2522    setTimeout(function () {
2523        successMessage.fadeOut();
2524    }, 5000);
2525}
2526
2527function contactHandleError(errorMsg = '') {
2528    const successMessage = $('.w-form-done');
2529    const errorMessage = $('.w-form-fail');
2530
2531    if (errorMsg !== '') {
2532        errorMessage.html('<div>' + errorMsg + '</div>');
2533    }
2534
2535    successMessage.fadeOut();
2536    errorMessage.fadeIn();
2537
2538    setTimeout(function () {
2539        errorMessage.fadeOut();
2540    }, 5000);
2541}
2542
2543function checkAkreditaceOtevrena() {
2544    const akreditaceForm = $('#accreditation-form');
2545    const akreditaceButton = akreditaceForm.find('.button');
2546
2547    if (akreditaceForm.length > 0) {
2548        $.ajax({
2549            url: '/wp-json/api/v1/config',
2550            method: 'GET',
2551            success: function (data) {
2552                if (data.condition === 60) {
2553                    $('#accreditation-form input').prop('disabled', true);
2554                    akreditaceButton.prop('disabled', true);
2555                    akreditaceBeforeEventError(data.message);
2556                }
2557
2558                if (data.condition === 1) {
2559                    $('#accreditation-form').hide()
2560                    akreditaceButton.prop('disabled', false);
2561                    akreditaceBeforeEventError(data.message);
2562                }
2563            },
2564            error: function () {
2565                console.log('Nastala chyba při načítání dat o configu');
2566            }
2567        });
2568    }
2569}
2570
2571// Akreditace form
2572function akreditaceFormSubmit() {
2573    const form = $('#accreditation-form');
2574    const submitButton = form.find('.button');
2575
2576    form.on('submit', function (e) {
2577        e.preventDefault(); // Prevent the form from submitting the traditional way
2578
2579        submitButton.val(submitButton.data('wait'));
2580
2581        // Gather the form data
2582        var formData = $(this).serialize();
2583
2584        var dataArray = $(this).serializeArray();
2585        var email = '';
2586        var telefon = '';
2587
2588        if (window.iti.isValidNumberPrecise()) {
2589            $.ajax({
2590                url: '/wp-json/api/v1/accreditation',
2591                method: 'POST',
2592                data: formData,
2593                success: function (data) {
2594                    let response = '';
2595                    let isJson = true;
2596
2597                    if (typeof data === 'string') {
2598                        try {
2599                            response = JSON.parse(data);
2600                        } catch (e) {
2601                            isJson = false;
2602                            response = data;
2603                        }
2604                    }
2605                    if (isJson && response.res === 1) {
2606                        dataArray.forEach(function (field) {
2607                            if (field.name === 's_email') {
2608                                email = field.value;
2609                            } else if (field.name === 's_phone') {
2610                                telefon = field.value;
2611                            }
2612                        });
2613                        if (window.iti) {
2614                            telefon = window.iti.getNumber();
2615                        }
2616                        dataLayer.push({
2617                            "event": "journalist_registration_sent",
2618                            "email": email,
2619                            "phone": telefon
2620                        });
2621
2622                        akreditaceHandleSuccess(form);
2623                    } else {
2624                        akreditaceHandleError(response.msg);
2625                    }
2626                },
2627                error: function () {
2628                    akreditaceHandleError();
2629                },
2630                complete: function () {
2631                    submitButton.val(submitButton.data('done'));
2632                }
2633            })
2634        } else {
2635            akreditaceHandleError('Zadejte platné telefonní číslo.');
2636            submitButton.val(submitButton.data('done'));
2637        }
2638    });
2639}
2640
2641function akreditaceHandleSuccess(form) {
2642    const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox');
2643    const agreementInput = form.find('#b_agree_vop');
2644    const agreementGDPRInput = form.find('#b_agree_gdpr');
2645    const successMessage = $('.w-form-done');
2646    const errorMessage = $('.w-form-fail');
2647
2648    successMessage.fadeIn();
2649    errorMessage.fadeOut();
2650
2651    agreementInput.prop('checked', false);
2652    agreementGDPRInput.prop('checked', false);
2653    agreementCustomCheck.each(function () {
2654        $(this).removeClass('w--redirected-checked');
2655    });
2656    $(form)[0].reset();
2657
2658    setTimeout(function () {
2659        successMessage.fadeOut();
2660    }, 5000);
2661}
2662
2663function akreditaceBeforeEventError(errorMsg = '') {
2664    const errorMessage = $('.w-form-fail.before-event');
2665
2666    if (errorMsg !== '') {
2667        errorMessage.find('.error-wrapper.before-event').html('<div>' + errorMsg + '</div>');
2668    }
2669
2670    errorMessage.fadeIn();
2671}
2672
2673function akreditaceHandleError(errorMsg = '', timeout = true) {
2674    const successMessage = $('.w-form-done');
2675    const errorMessage = $('.w-form-fail');
2676
2677    if (errorMsg !== '') {
2678        errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>');
2679    }
2680
2681    successMessage.fadeOut();
2682    errorMessage.fadeIn();
2683
2684    if (timeout) {
2685        setTimeout(function () {
2686            errorMessage.fadeOut();
2687        }, 5000);
2688    }
2689}
2690
2691// Vstupenka od vystavovatele form
2692function exhibitorTicketFormSubmit() {
2693    const form = $('#exhibitor-ticket-form');
2694    const submitButton = form.find('.button');
2695    const urlParams = new URLSearchParams(window.location.search);
2696    const vParam = urlParams.get('v');
2697
2698    form.on('submit', function (e) {
2699        e.preventDefault(); // Prevent the form from submitting the traditional way
2700
2701        submitButton.val(submitButton.data('wait'));
2702
2703        // Gather the form data
2704        var formData = $(this).serialize();
2705
2706        // Make the API call
2707        $.ajax({
2708            url: '/wp-json/api/v1/exhibitor-ticket',
2709            method: 'POST',
2710            data: {
2711                formData: formData,
2712                parameter: vParam
2713            },
2714            success: function (data) {
2715                let response = '';
2716                let isJson = true;
2717
2718                if (typeof data === 'string') {
2719                    try {
2720                        response = JSON.parse(data);
2721                    } catch (e) {
2722                        isJson = false;
2723                        response = data;
2724                    }
2725                }
2726                if (isJson && response.res === 1) {
2727                    exhibitorTicketHandleSuccess(form);
2728                } else {
2729                    if (isJson) {
2730                        exhibitorTicketHandleError(response.msg);
2731                    } else {
2732                        exhibitorTicketHandleError(response);
2733                    }
2734                }
2735            },
2736            error: function () {
2737                exhibitorTicketHandleError();
2738            },
2739            complete: function () {
2740                submitButton.val(submitButton.data('done'));
2741            }
2742        })
2743    });
2744}
2745
2746function exhibitorTicketHandleSuccess(form) {
2747    const agreementCustomCheck = form.find('.w-checkbox-input--inputType-custom.checkbox');
2748    const agreementInput = form.find('#b_agree_vop');
2749    const agreementGDPRInput = form.find('#b_agree_gdpr');
2750    const successMessage = $('.w-form-done');
2751    const errorMessage = $('.w-form-fail');
2752
2753    successMessage.fadeIn();
2754    errorMessage.fadeOut();
2755
2756    agreementInput.prop('checked', false);
2757    agreementGDPRInput.prop('checked', false);
2758    agreementCustomCheck.each(function () {
2759        $(this).removeClass('w--redirected-checked');
2760    });
2761    $(form)[0].reset();
2762
2763    setTimeout(function () {
2764        successMessage.fadeOut();
2765    }, 5000);
2766}
2767
2768function exhibitorTicketHandleError(errorMsg = '') {
2769    const successMessage = $('.w-form-done');
2770    const errorMessage = $('.w-form-fail');
2771
2772    if (errorMsg !== '') {
2773        errorMessage.find('.error-wrapper').html('<div>' + errorMsg + '</div>');
2774    }
2775
2776    successMessage.fadeOut();
2777    errorMessage.fadeIn();
2778
2779    setTimeout(function () {
2780        errorMessage.fadeOut();
2781    }, 5000);
2782}
2783
2784function appendFieldListObory(fieldWrapper, data) {
2785    // Clear the wrapper before adding new content
2786    fieldWrapper.empty();
2787
2788    Object.keys(data).forEach(function (key) {
2789        const item = data[key];
2790        const decodedName = safeDecodeURIComponent(item.s_name).replace(/\+/g, ' ');
2791
2792        const labelHTML = `
2793                    <li class="field-list-item">
2794                        <div class="dot-wrapper">
2795                            <div class="dot-icon"></div>
2796                         </div>
2797                        <div>
2798                            ${decodedName}
2799                        </div>
2800                    </li>
2801                `;
2802
2803        // Append the created label to the wrapper
2804        fieldWrapper.append(labelHTML);
2805    });
2806}
2807
2808function initKatalogTabs() {
2809    $('.js-katalog-tabs').on('change', 'input[type="radio"]', function () {
2810        var tab = $(this).closest('label').data('tab');
2811        if (!tab) return;
2812
2813        var $interactableWrapper = $(this).closest('.interactable-part-wrapper-catalog');
2814
2815        // If not found (new layout), try finding via common parent
2816        if ($interactableWrapper.length === 0) {
2817            $interactableWrapper = $(this).closest('.newsletter-card-wrapper').find('.interactable-part-wrapper-catalog');
2818        }
2819
2820        var $listFilter = $interactableWrapper.find('.list-with-filter');
2821
2822        // Reset: hide field detail and company detail, restore list only if it was hidden
2823        $interactableWrapper.find('.field-detail-wrapper').remove();
2824        $interactableWrapper.find('.obory-nav').remove();
2825        $interactableWrapper.find('.obory-exhibitors').remove();
2826        $interactableWrapper.find('.plan-hall-topbar').remove();
2827        $interactableWrapper.find('.plan-hall-detail').remove();
2828        $interactableWrapper.find('.plan-halls-grid').show();
2829        if (mainPlanImageUrl) {
2830            $interactableWrapper.find('.plan-map-image').attr('src', mainPlanImageUrl);
2831            ensurePlanMapLightbox($interactableWrapper, mainPlanImageUrl);
2832        }
2833        $interactableWrapper.find('.company-detail-wrapper-new').hide();
2834        if ($listFilter.is(':hidden')) {
2835            $listFilter.css('display', '');
2836        }
2837
2838        // Show/hide panels
2839        $listFilter.find('.js-katalog-panel').addClass('is-hidden');
2840        $listFilter.find('.js-katalog-panel[data-panel="' + tab + '"]').removeClass('is-hidden');
2841
2842        // Update URL params when switching tabs (clear filter params)
2843        if (tab === 'companies') {
2844            updateCatalogUrl({});
2845        } else {
2846            updateCatalogUrl({ tab: tab });
2847        }
2848
2849        // Reset obory hierarchy to root when switching to the fields tab
2850        if (tab === 'fields' && oboryFullData) {
2851            var $fieldsPanel = $listFilter.find('.js-katalog-panel[data-panel="fields"]');
2852            if ($fieldsPanel.length > 0) {
2853                renderFieldsLevel($fieldsPanel, '');
2854            }
2855        }
2856
2857        // Show/hide search & sort for companies tab only
2858        var isCompanies = (tab === 'companies');
2859        if (isCompanies) {
2860            // Remove inline styles so CSS controls visibility (preserves media query rules)
2861            $listFilter.find('.headline-and-search').css('display', '');
2862            $listFilter.children('.sort-buttons-wrapper').css('display', '');
2863            $listFilter.children('.filter-reset').css('display', '');
2864        } else {
2865            $listFilter.find('.headline-and-search').hide();
2866            $listFilter.children('.sort-buttons-wrapper').hide();
2867            $listFilter.children('.filter-reset').hide();
2868        }
2869    });
2870}
2871
2872function appendExhibitorListNew(exhibitorListWrapper, data, isAscending, sortBy) {
2873    const sortedData = Object.keys(data)
2874        .map(key => data[key])
2875        .sort((a, b) => {
2876            let valA, valB;
2877
2878            if (sortBy === 'name') {
2879                valA = safeDecodeURIComponent(a.s_nazev).toLowerCase();
2880                valB = safeDecodeURIComponent(b.s_nazev).toLowerCase();
2881            } else if (sortBy === 'hala') {
2882                valA = safeDecodeURIComponent(a.s_hala).toLowerCase();
2883                valB = safeDecodeURIComponent(b.s_hala).toLowerCase();
2884            }
2885
2886            if (valA < valB) return isAscending ? -1 : 1;
2887            if (valA > valB) return isAscending ? 1 : -1;
2888            return 0;
2889        });
2890
2891    let isFieldDetailList = exhibitorListWrapper.hasClass('field-detail-exhibitor-list') || exhibitorListWrapper.hasClass('plan');
2892
2893    exhibitorListWrapper.empty();
2894
2895    if (sortedData.length > 0) {
2896        sortedData.forEach(function (item) {
2897            const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' ');
2898            const decodedHala = safeDecodeURIComponent(item.s_hala);
2899            const decodedStanek = safeDecodeURIComponent(item.s_stanek);
2900            const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' ');
2901            const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' ');
2902            const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' ');
2903            let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim();
2904            let znackyArray = item.znacky_names;
2905            let description = safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' ');
2906            let fileUrl = buildLogoSrc(item);
2907
2908            if (!website.startsWith("http://") && !website.startsWith("https://") && website !== '') {
2909                website = "https://" + website;
2910            }
2911
2912            let halaStanek = '';
2913            if (decodedHala !== '++++++') {
2914                halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`;
2915            } else if (decodedStanek !== '') {
2916                halaStanek = `--${decodedStanek}`;
2917            } else {
2918                halaStanek = '';
2919            }
2920
2921            let companyData = {
2922                halaStanek: halaStanek,
2923                name: decodedName,
2924                description: description,
2925                address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip,
2926                email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(),
2927                emails: safeDecodeURIComponent(item.s_email).split(',').map(e => e.trim()).filter(e => e),
2928                phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' ').replace(/\//g, ''),
2929                phones: safeDecodeURIComponent(item.s_tel1).split(',').map(p => p.trim().replace(/(?!^)\+/g, ' ').replace(/\//g, '')).filter(p => p && p !== '+'),
2930                www: website,
2931                urls: safeDecodeURIComponent(item.s_www).split(',').map(u => u.trim()).filter(u => u),
2932                categories: item.category_names,
2933                category_codes: item.category_codes,
2934                fileUrl: fileUrl,
2935                znacky_names: znackyArray,
2936                destinace_names: item.destinace_names,
2937                najdete_na_veletrhu: item.s_najdete_na_veletrhu
2938            };
2939
2940            let descPreview = description.length > 200 ? description.substring(0, 200) + '...' : description;
2941
2942            const labelHTML = `
2943                <li class="companies-list-item">
2944                    <a href="#" class="company-detail-katalog-link company-detail-katalog-link-new ${isFieldDetailList ? 'company-detail-from-field' : ''} w-inline-block flex-column p4 gap-16px">
2945                        <div class="flex-row flex-justify-between gap-16px">
2946                            <div class="flex-column gap-16px">
2947                                <div class="company-name katalog">${decodedName}</div>
2948                                ${halaStanek ? `
2949                                <ul role="list" class="list-column-wrapper">
2950                                    <li class="field-list-item">
2951                                        <div class="icon-wrapper">
2952                                            <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/pointer.svg" />
2953                                        </div>
2954                                        <div>Hala - stánek: ${halaStanek}</div>
2955                                    </li>
2956                                </ul>
2957                                ` : ''}
2958                            </div>
2959                            ${fileUrl ? `
2960                            <div class="logo">
2961                                <img src="${fileUrl}" class="img" />
2962                            </div>
2963                            ` : ''}
2964                        </div>
2965                        ${descPreview ? `<p class="paragraph under-list shady-text">${descPreview}</p>` : ''}
2966                    </a>
2967                </li>
2968            `;
2969
2970            const $label = $(labelHTML).appendTo(exhibitorListWrapper);
2971            if (isFieldDetailList) {
2972                $label.find('.company-detail-from-field').data('company', companyData);
2973            } else {
2974                $label.find('.company-detail-katalog-link-new').data('company', companyData);
2975            }
2976        });
2977
2978        if (!isFieldDetailList) {
2979            getDetailDataNew($('.company-detail-katalog-link-new').not('.company-detail-from-field'));
2980        }
2981    } else {
2982        exhibitorListWrapper.append(`
2983            <li class="companies-list-item">
2984                <div class="company-name katalog">Vyhledávání neodpovídají žádní vystavující</div>
2985            </li>
2986        `);
2987    }
2988
2989    // Auto-open exhibitor if restoring from URL
2990    if (pendingExhibitorRestore) {
2991        var name = pendingExhibitorRestore;
2992        exhibitorListWrapper.find('.company-detail-from-field').each(function () {
2993            var data = $(this).data('company');
2994            if (data && data.name === name) {
2995                pendingExhibitorRestore = null;
2996                $(this).trigger('click');
2997                return false;
2998            }
2999        });
3000    }
3001
3002    Webflow.require('ix2').init();
3003}
3004
3005function populateCompanyDetail(companyData, $detail) {
3006    const $readHeight = $detail.find('#readHeight');
3007    $readHeight.addClass('w-100');
3008
3009    // Company name (first .fields-headline in the top section)
3010    $readHeight.children('.flex-column').first().find('.fields-headline').first().text(companyData.name);
3011
3012    // Hall/stand
3013    const $hallList = $readHeight.children('.flex-column').first().find('.list-column-wrapper');
3014    $hallList.empty();
3015    if (companyData.halaStanek) {
3016        $hallList.append(`
3017            <li class="field-list-item">
3018                <div class="icon-wrapper">
3019                    <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/pointer.svg" />
3020                </div>
3021                <div class="company-info-list-text">Hala - stánek: ${companyData.halaStanek}</div>
3022            </li>
3023        `);
3024        $hallList.show();
3025    } else {
3026        $hallList.hide();
3027    }
3028
3029    // Logo
3030    const $logoContainer = $readHeight.children('.flex-column').first().find('.logo');
3031    const $logo = $logoContainer.find('img');
3032    if (companyData.fileUrl) {
3033        $logo.attr('src', companyData.fileUrl);
3034        $logoContainer.show();
3035    } else {
3036        $logoContainer.hide();
3037    }
3038
3039    // Description
3040    const $desc = $readHeight.children('.flex-column').first().find('.paragraph.under-list');
3041    if (companyData.description) {
3042        $desc.text(companyData.description).show();
3043    } else {
3044        $desc.hide();
3045    }
3046
3047    // Address
3048    const $addressItem = $detail.find('#company-detail-address');
3049    if (companyData.address && companyData.address.trim() !== '' && companyData.address.trim() !== ',') {
3050        $addressItem.find('.company-info-list-text').html(
3051            companyData.address.replace(/, /g, '<br>')
3052        );
3053        $addressItem.show();
3054    } else {
3055        $addressItem.hide();
3056    }
3057
3058    // Email
3059    const $emailItem = $detail.find('#company-detail-email');
3060    $detail.find('.extra-email').remove();
3061    const emails = (companyData.emails && companyData.emails.length > 0) ? companyData.emails : (companyData.email ? [companyData.email] : []);
3062    const validEmails = emails.filter(e => e && e !== '' && e !== '+');
3063    if (validEmails.length > 0) {
3064        $emailItem.find('.company-info-list-text').attr('href', 'mailto:' + validEmails[0]).text(validEmails[0]);
3065        $emailItem.show();
3066        validEmails.slice(1).forEach(function (email) {
3067            const $clone = $emailItem.clone().removeAttr('id').addClass('extra-email');
3068            $clone.find('.company-info-list-text').attr('href', 'mailto:' + email).text(email);
3069            $emailItem.after($clone);
3070        });
3071    } else {
3072        $emailItem.hide();
3073    }
3074
3075    // Phone
3076    const $phoneItem = $detail.find('#company-detail-phone');
3077    $detail.find('.extra-phone').remove();
3078    const phones = (companyData.phones && companyData.phones.length > 0) ? companyData.phones : (companyData.phone ? [companyData.phone] : []);
3079    const validPhones = phones.filter(p => p && p !== '' && p !== '+');
3080    if (validPhones.length > 0) {
3081        $phoneItem.find('.company-info-list-text').attr('href', 'tel:' + validPhones[0].replace(/\//g, '')).text(validPhones[0]);
3082        $phoneItem.show();
3083        validPhones.slice(1).forEach(function (phone) {
3084            const $clone = $phoneItem.clone().removeAttr('id').addClass('extra-phone');
3085            $clone.find('.company-info-list-text').attr('href', 'tel:' + phone.replace(/\//g, '')).text(phone);
3086            $phoneItem.after($clone);
3087        });
3088    } else {
3089        $phoneItem.hide();
3090    }
3091
3092    // Website
3093    const $webListItems = $detail.find('#company-detail-web');
3094    $detail.find('.extra-www').remove();
3095    const urls = (companyData.urls && companyData.urls.length > 0) ? companyData.urls : (companyData.www ? [companyData.www] : []);
3096    const validUrls = urls.filter(u => u && u !== '');
3097    if (validUrls.length > 0) {
3098        $webListItems.find('.company-website').attr('href', validUrls[0]).text(validUrls[0].replace(/^https?:\/\//, ''));
3099        $webListItems.show();
3100        validUrls.slice(1).forEach(function (url) {
3101            const $clone = $webListItems.clone().removeAttr('id').addClass('extra-www');
3102            $clone.find('.company-website').attr('href', url).text(url.replace(/^https?:\/\//, ''));
3103            $webListItems.after($clone);
3104        });
3105    } else {
3106        $webListItems.hide();
3107    }
3108
3109    // Obory/Categories
3110    const $obory = $detail.find('#company-detail-obory');
3111    const $oboryContainer = $obory.find('.catalog-filter-detail');
3112    $oboryContainer.empty();
3113
3114    if (companyData.categories && companyData.categories.length > 0) {
3115        let hasValid = false;
3116        companyData.categories.forEach(function (category, idx) {
3117            if (category === '') return;
3118            hasValid = true;
3119            let categoryName = safeDecodeURIComponent(category).replace(/\+/g, ' ');
3120            let categoryCode = (companyData.category_codes && companyData.category_codes[idx]) ? companyData.category_codes[idx] : '';
3121            let innerHTML = `
3122                    <div class="icon-wrapper">
3123                         <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/files.svg" />
3124                    </div>
3125                    <div>${categoryName}</div>
3126            `;
3127            if (categoryCode) {
3128                $oboryContainer.append(`
3129                    <a href="${buildCatalogFilterUrl({ tab: 'fields', obor: categoryCode })}" data-filter-type="obor" data-obor="${categoryCode}" class="company-detail-info-list-item company-detail-info-list-item--flex field-list-item flex-align-center catalog-filter-link">${innerHTML}</a>
3130                `);
3131            } else {
3132                $oboryContainer.append(`
3133                    <div class="company-detail-info-list-item company-detail-info-list-item--flex field-list-item flex-align-center">${innerHTML}</div>
3134                `);
3135            }
3136        });
3137        if (hasValid) {
3138            $obory.show().prev('.companies-list-header').show();
3139        } else {
3140            $obory.hide().prev('.companies-list-header').hide();
3141        }
3142    } else {
3143        $obory.hide().prev('.companies-list-header').hide();
3144    }
3145
3146    // Znacky (first element with id company-detail-znacky)
3147    const $znacky = $detail.find('#company-detail-znacky').first();
3148    const $znackyContainer = $znacky.find('.catalog-filter-detail');
3149    $znackyContainer.empty();
3150    let hasValidZnacky = false;
3151
3152    if (companyData.znacky_names && companyData.znacky_names.length > 0) {
3153        companyData.znacky_names.forEach(function (znacka) {
3154            if (znacka) {
3155                hasValidZnacky = true;
3156                $znackyContainer.append(`
3157                    <div class="company-detail-info-list-item company-detail-info-list-item--flex_4 flex-column field-list-item gap-16px">
3158                        <div>${znacka}</div>
3159                    </div>
3160                `);
3161            }
3162        });
3163    }
3164
3165    if (hasValidZnacky) {
3166        $znacky.show().prev('.companies-list-header').show();
3167    } else {
3168        $znacky.hide().prev('.companies-list-header').hide();
3169    }
3170
3171    // Destinace
3172    const $destinace = $detail.find('#company-detail-destinace');
3173    const $destinaceContainer = $destinace.find('.catalog-filter-detail');
3174    $destinaceContainer.empty();
3175    let hasValidDestinace = false;
3176
3177    if (companyData.destinace_names && companyData.destinace_names.length > 0) {
3178        companyData.destinace_names.forEach(function (destinace) {
3179            if (destinace) {
3180                hasValidDestinace = true;
3181                let continentName = getContinentForDestination(destinace);
3182                if (continentName) {
3183                    $destinaceContainer.append(`
3184                        <a href="${buildCatalogFilterUrl({ tab: 'destinations', continent: continentName, destination: destinace })}" data-filter-type="destination" data-continent="${continentName}" data-destination="${destinace}" class="company-detail-info-list-item company-detail-info-list-item--flex_4 flex-column field-list-item gap-16px catalog-filter-link">
3185                            <div>${destinace}</div>
3186                        </a>
3187                    `);
3188                } else {
3189                    $destinaceContainer.append(`
3190                        <div class="company-detail-info-list-item company-detail-info-list-item--flex_4 flex-column field-list-item gap-16px">
3191                            <div>${destinace}</div>
3192                        </div>
3193                    `);
3194                }
3195            }
3196        });
3197    }
3198
3199    if (hasValidDestinace) {
3200        $destinace.show().prev('.companies-list-header').show();
3201    } else {
3202        $destinace.hide().prev('.companies-list-header').hide();
3203    }
3204
3205    // Najdete na veletrhu (first element with id company-detail-locations)
3206    const $locations = $detail.find('#company-detail-locations').first();
3207    const $locationsContainer = $locations.find('.catalog-filter-detail');
3208    $locationsContainer.empty();
3209    let hasValidLocations = false;
3210
3211    if (companyData.najdete_na_veletrhu && companyData.najdete_na_veletrhu.length > 0) {
3212        companyData.najdete_na_veletrhu.forEach(function (loc) {
3213            if (loc) {
3214                hasValidLocations = true;
3215                const linkTarget = loc.link_target || '_blank';
3216                const linkTitle = loc.link_title || loc.title;
3217                $locationsContainer.append(`
3218                    <div class="blog-card-link flex-row w-inline-block">
3219                        ${loc.thumbnail ? `
3220                        <div class="blog-card-image-wrapper img-w-30">
3221                            <img src="${loc.thumbnail}" class="blog-image" />
3222                        </div>
3223                        ` : ''}
3224                        <div class="blog-content-wrapper">
3225                            <div class="blog-text-wrapper">
3226                                <div class="flex-row gap-8px flex-align-end flex-justify-between blog-container-reverse">
3227                                    <div class="flex-column">
3228                                        <div class="blog-card-headline pb-2">${loc.title}</div>
3229                                    </div>
3230                                    ${loc.logo ? `
3231                                    <div class="img-w-130">
3232                                        <img src="${loc.logo}" class="img" />
3233                                    </div>
3234                                    ` : ''}
3235                                </div>
3236                                ${loc.description ? `<p class="paragraph blog-card-description no-line">${loc.description}</p>` : ''}
3237                                ${loc.url ? `
3238                                <a href="${loc.url}" target="${linkTarget}">
3239                                    <div class="blog-card-bottom-wrapper">
3240                                        <div>${linkTitle}</div>
3241                                        <div class="icon-18px w-embed" style="transform: translate3d(0px, 0px, 0px) scale3d(1, 1, 1) rotateX(0deg) rotateY(0deg) rotateZ(0deg) skew(0deg, 0deg); transform-style: preserve-3d;">
3242                                            <svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
3243                                                <path d="M15.7499 9.49988H2.24989M15.7499 9.49988L11.2499 4.99988M15.7499 9.49988L11.2499 13.9999" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
3244                                            </svg>
3245                                        </div>
3246                                    </div>
3247                                </a>
3248                                ` : ''}
3249                            </div>
3250                        </div>
3251                    </div>
3252                `);
3253            }
3254        });
3255    }
3256
3257    if (hasValidLocations) {
3258        $locations.show().prev('.companies-list-header').show();
3259    } else {
3260        $locations.hide().prev('.companies-list-header').hide();
3261    }
3262}
3263
3264function renderFieldDetailExhibitors($listWrapper, data, isAscending = true, sortBy = 'name') {
3265    const sortedData = Object.keys(data)
3266        .map(key => data[key])
3267        .sort((a, b) => {
3268            let valA, valB;
3269
3270            if (sortBy === 'name') {
3271                valA = safeDecodeURIComponent(a.s_nazev).toLowerCase();
3272                valB = safeDecodeURIComponent(b.s_nazev).toLowerCase();
3273            } else if (sortBy === 'hala') {
3274                valA = safeDecodeURIComponent(a.s_hala).toLowerCase();
3275                valB = safeDecodeURIComponent(b.s_hala).toLowerCase();
3276            }
3277
3278            if (valA < valB) return isAscending ? -1 : 1;
3279            if (valA > valB) return isAscending ? 1 : -1;
3280            return 0;
3281        });
3282
3283    $listWrapper.empty();
3284
3285    if (sortedData.length > 0) {
3286        sortedData.forEach(function (item) {
3287            const decodedName = safeDecodeURIComponent(item.s_nazev).replace(/\+/g, ' ');
3288            const decodedHala = safeDecodeURIComponent(item.s_hala);
3289            const decodedStanek = safeDecodeURIComponent(item.s_stanek);
3290            const decodedStreet = safeDecodeURIComponent(item.s_street).replace(/\+/g, ' ');
3291            const decodedCity = safeDecodeURIComponent(item.s_city).replace(/\+/g, ' ');
3292            const decodedZip = safeDecodeURIComponent(item.s_zip).replace(/\+/g, ' ');
3293            let website = safeDecodeURIComponent(item.s_www).split(',')[0].trim();
3294            let znackyArray = item.znacky_names;
3295            let description = safeDecodeURIComponent(item.s_profil).replace(/\+/g, ' ');
3296            let fileUrl = buildLogoSrc(item);
3297
3298            if (!website.startsWith("http://") && !website.startsWith("https://") && website !== '') {
3299                website = "https://" + website;
3300            }
3301
3302            let halaStanek = '';
3303            if (decodedHala !== '++++++') {
3304                halaStanek = decodedStanek !== '' ? `${decodedHala}-${decodedStanek}` : `${decodedHala}--`;
3305            } else if (decodedStanek !== '') {
3306                halaStanek = `--${decodedStanek}`;
3307            } else {
3308                halaStanek = '';
3309            }
3310
3311            let companyData = {
3312                halaStanek: halaStanek,
3313                name: decodedName,
3314                description: description,
3315                address: decodedStreet + ' ' + decodedCity + ', ' + decodedZip,
3316                email: safeDecodeURIComponent(item.s_email).split(',')[0].trim(),
3317                emails: safeDecodeURIComponent(item.s_email).split(',').map(e => e.trim()).filter(e => e),
3318                phone: safeDecodeURIComponent(item.s_tel1).split(',')[0].trim().replace(/(?!^)\+/g, ' ').replace(/\//g, ''),
3319                phones: safeDecodeURIComponent(item.s_tel1).split(',').map(p => p.trim().replace(/(?!^)\+/g, ' ').replace(/\//g, '')).filter(p => p && p !== '+'),
3320                www: website,
3321                urls: safeDecodeURIComponent(item.s_www).split(',').map(u => u.trim()).filter(u => u),
3322                categories: item.category_names,
3323                category_codes: item.category_codes,
3324                fileUrl: fileUrl,
3325                znacky_names: znackyArray,
3326                destinace_names: item.destinace_names,
3327                najdete_na_veletrhu: item.s_najdete_na_veletrhu
3328            };
3329
3330            let descPreview = description.length > 200 ? description.substring(0, 200) + '...' : description;
3331
3332            const labelHTML = `
3333                <li class="companies-list-item">
3334                    <a href="#" class="company-detail-katalog-link company-detail-katalog-link-new company-detail-from-field w-inline-block flex-column p4 gap-16px">
3335                        <div class="flex-row flex-justify-between gap-16px">
3336                            <div class="flex-column gap-16px">
3337                                <div class="company-name katalog">${decodedName}</div>
3338                                ${halaStanek ? `
3339                                <ul role="list" class="list-column-wrapper">
3340                                    <li class="field-list-item">
3341                                        <div class="icon-wrapper">
3342                                            <img class="icon-16px w-flex w-embed" src="${customBlockData.pluginUrl}images/pointer.svg" />
3343                                        </div>
3344                                        <div>Hala - stánek: ${halaStanek}</div>
3345                                    </li>
3346                                </ul>
3347                                ` : ''}
3348                            </div>
3349                            ${fileUrl ? `
3350                            <div class="logo">
3351                                <img src="${fileUrl}" class="img" />
3352                            </div>
3353                            ` : ''}
3354                        </div>
3355                        ${descPreview ? `<p class="paragraph under-list shady-text">${descPreview}</p>` : ''}
3356                    </a>
3357                </li>
3358            `;
3359
3360            const $label = $(labelHTML).appendTo($listWrapper);
3361            $label.find('.company-detail-from-field').data('company', companyData);
3362        });
3363    } else {
3364        $listWrapper.append(`
3365            <li class="companies-list-item">
3366                <div class="company-name katalog">Vyhledávání neodpovídají žádní vystavující</div>
3367            </li>
3368        `);
3369    }
3370
3371    // Auto-open exhibitor if restoring from URL
3372    if (pendingExhibitorRestore) {
3373        var name = pendingExhibitorRestore;
3374        $listWrapper.find('.company-detail-from-field').each(function () {
3375            var data = $(this).data('company');
3376            if (data && data.name === name) {
3377                pendingExhibitorRestore = null;
3378                $(this).trigger('click');
3379                return false;
3380            }
3381        });
3382    }
3383}
3384
3385function getDetailDataNew($link) {
3386    $link.on('click', function (e) {
3387        const companyData = $(this).data('company');
3388        if (!companyData) return;
3389
3390        // Pokud je link zároveň .company-detail-from-field, ten má vlastní handler
3391        // s správným navigation-source — necháme ho zpracovat celou logiku
3392        if ($(this).hasClass('company-detail-from-field')) return;
3393
3394        e.preventDefault();
3395        setExhibitorParam(companyData.name);
3396
3397        // Find detail in the same block/wrapper
3398        let $detail = $(this).closest('.newsletter-card-wrapper').find('.company-detail-wrapper-new');
3399
3400        // Fallback or specific selection
3401        if ($detail.length === 0) {
3402            $detail = $('.company-detail-wrapper-new');
3403        }
3404
3405        // Set navigation source to list (regular exhibitor list)
3406        $detail.data('navigation-source', 'list');
3407
3408        // Show detail, hide list
3409        $(this).closest('.interactable-part-wrapper-catalog').find('.list-with-filter').hide();
3410        $detail.css('display', 'flex');
3411
3412        $('html, body').animate({ scrollTop: $('#detail').offset().top }, 300);
3413
3414        populateCompanyDetail(companyData, $detail);
3415    });
3416
3417    // Back button handler
3418    const $detail = $('.company-detail-wrapper-new');
3419    $detail.find('.back-katalog-button').off('click').on('click', function (e) {
3420        e.preventDefault();
3421        e.stopImmediatePropagation();
3422        setExhibitorParam(null);
3423        var source = $detail.data('navigation-source');
3424        $detail.hide().css('opacity', '');
3425        if (source === 'obory-drilled') {
3426            // Return to the obory drilled-in view (list-with-filter has panel + exhibitors)
3427            $('.interactable-part-wrapper-catalog .list-with-filter').css('display', '');
3428        } else if (source === 'plan-hall') {
3429            // Return to the plan hall exhibitor view
3430            $('.interactable-part-wrapper-catalog .list-with-filter').css('display', '');
3431            $('.interactable-part-wrapper-catalog .plan-hall-topbar').css('display', 'flex');
3432        } else if (source === 'field-detail') {
3433            // Return to the field detail view
3434            $('.interactable-part-wrapper-catalog .list-with-filter').hide();
3435            $('.interactable-part-wrapper-catalog .field-detail-wrapper').css('display', 'flex');
3436            // Restore URL to reflect the field-detail state (bez exhibitor parametru)
3437            var activeTab = $('.js-katalog-tabs input[type="radio"]:checked').closest('label').data('tab') || 'companies';
3438            if (activeTab === 'destinations') {
3439                var $fw = $('.interactable-part-wrapper-catalog .field-detail-wrapper');
3440                var restoredContinent = $fw.data('continent') || '';
3441                var restoredDestination = $fw.find('h2.fields-headline').text() || '';
3442                // Nadpis může být "9.2. Guatemala" — extrahujeme jen jméno bez kódu
3443                restoredDestination = restoredDestination.replace(/^\d+(\.\d+)*\.\s*/, '');
3444                var urlParams = { tab: 'destinations', continent: restoredContinent };
3445                if (restoredDestination && restoredDestination !== restoredContinent) {
3446                    urlParams.destination = restoredDestination;
3447                }
3448                updateCatalogUrl(urlParams);
3449            }
3450            // Re-hide/re-show po Webflow IX2 animaci
3451            setTimeout(function () {
3452                $('.interactable-part-wrapper-catalog .list-with-filter').hide();
3453                $('.interactable-part-wrapper-catalog .field-detail-wrapper').css('display', 'flex');
3454            }, 50);
3455        } else {
3456            // Return to the regular list
3457            $('.interactable-part-wrapper-catalog .field-detail-wrapper').remove();
3458            $('.interactable-part-wrapper-catalog .list-with-filter').css('display', '');
3459        }
3460    });
3461}
3462
3463function appendFieldsPanelNew(data) {
3464    const $panel = $('.js-katalog-panel[data-panel="fields"]');
3465    if ($panel.length === 0) return;
3466    oboryFullData = data;
3467    renderFieldsLevel($panel, '');
3468}
3469
3470function getFieldChildren(parentCode) {
3471    if (!oboryFullData) return [];
3472
3473    // Determine the depth we're looking for
3474    // '' (root) -> depth 1 (codes like '1.')
3475    // '1.' -> depth 2 (codes like '1.2.')
3476    var parentDepth = 0;
3477    if (parentCode) {
3478        parentDepth = parentCode.replace(/\.$/, '').split('.').length;
3479    }
3480    var childDepth = parentDepth + 1;
3481
3482    // Build a set of all codes in the data
3483    var allCodes = {};
3484    Object.keys(oboryFullData).forEach(function (key) {
3485        if (key === '') return;
3486        var item = oboryFullData[key];
3487        allCodes[item.s_code] = item;
3488    });
3489
3490    // Find all codes that are descendants of parentCode
3491    var descendantCodes = {};
3492    Object.keys(allCodes).forEach(function (code) {
3493        if (parentCode === '') {
3494            // All codes are descendants of root
3495            descendantCodes[code] = true;
3496        } else if (code.indexOf(parentCode) === 0 && code !== parentCode) {
3497            descendantCodes[code] = true;
3498        }
3499    });
3500
3501    // Group descendants by their direct child prefix at childDepth
3502    var childrenMap = {};
3503    Object.keys(descendantCodes).forEach(function (code) {
3504        var parts = code.replace(/\.$/, '').split('.');
3505        if (parts.length >= childDepth) {
3506            var childCode = parts.slice(0, childDepth).join('.') + '.';
3507            if (!childrenMap[childCode]) {
3508                childrenMap[childCode] = true;
3509            }
3510        }
3511    });
3512
3513    // Build result array, using real data if available or virtual entries
3514    var result = [];
3515    Object.keys(childrenMap).forEach(function (childCode) {
3516        if (allCodes[childCode]) {
3517            result.push({
3518                s_code: allCodes[childCode].s_code,
3519                s_name: safeDecodeURIComponent(allCodes[childCode].s_name).replace(/\+/g, ' ')
3520            });
3521        } else {
3522            // Virtual parent - intermediate code missing from IS data
3523            result.push({
3524                s_code: childCode,
3525                s_name: childCode,
3526                virtual: true
3527            });
3528        }
3529    });
3530
3531    // Sort numerically by code segments
3532    result.sort(function (a, b) {
3533        var aParts = a.s_code.replace(/\.$/, '').split('.').map(Number);
3534        var bParts = b.s_code.replace(/\.$/, '').split('.').map(Number);
3535        for (var i = 0; i < Math.max(aParts.length, bParts.length); i++) {
3536            var aVal = aParts[i] || 0;
3537            var bVal = bParts[i] || 0;
3538            if (aVal !== bVal) return aVal - bVal;
3539        }
3540        return 0;
3541    });
3542
3543    return result;
3544}
3545
3546function renderFieldsLevel($panel, parentCode) {
3547    // Store the current obor level for URL restoration
3548    $panel.data('current-obor', parentCode);
3549
3550    // Remove any previous obory navigation elements (back button + exhibitor section)
3551    $panel.siblings('.obory-nav').remove();
3552    $panel.siblings('.obory-exhibitors').remove();
3553    $panel.empty();
3554    // Reset panel inline styles from drilled-in view
3555    $panel.css({ 'background': '', 'border-radius': '', 'padding': '' });
3556
3557    var children = getFieldChildren(parentCode);
3558
3559    if (parentCode) {
3560        // --- Drilled-in view: white card with plain text grid (Figma design) ---
3561        var parts = parentCode.replace(/\.$/, '').split('.');
3562        var grandparentCode = parts.length > 1 ? parts.slice(0, -1).join('.') + '.' : '';
3563
3564        var currentName = parentCode;
3565        if (oboryFullData) {
3566            Object.keys(oboryFullData).forEach(function (key) {
3567                if (key === '') return;
3568                if (oboryFullData[key].s_code === parentCode) {
3569                    currentName = safeDecodeURIComponent(oboryFullData[key].s_name).replace(/\+/g, ' ');
3570                }
3571            });
3572        }
3573
3574        // Back button + search input as a sibling BEFORE the panel
3575        var navHTML = `
3576            <div class="obory-nav" style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:16px;">
3577                <a href="#" class="back-katalog-button obory-back-button w-inline-block" data-parent="${grandparentCode}">
3578                    <div class="back-icon w-embed">
3579                        <svg width="16" height="17" viewbox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
3580                            <path d="M2 8.5H14M2 8.5L6 12.5M2 8.5L6 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
3581                        </svg>
3582                    </div>
3583                    <div>Zpět</div>
3584                </a>
3585                <form class="search full-width-mobile w-form obory-search-form" onsubmit="return false;" style="margin:0;">
3586                    <input class="search-input w-input obory-search-input" maxlength="256" placeholder="Hledat…" type="search" />
3587                    <div class="icon-24px search-input-icon w-embed">
3588                        <svg width="24" height="24" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
3589                            <path d="M21 21L15 15M3 10C3 10.9193 3.18106 11.8295 3.53284 12.6788C3.88463 13.5281 4.40024 14.2997 5.05025 14.9497C5.70026 15.5998 6.47194 16.1154 7.32122 16.4672C8.17050 16.8189 9.08075 17 10 17C10.9193 17 11.8295 16.8189 12.6788 16.4672C13.5281 16.1154 14.2997 15.5998 14.9497 14.9497C15.5998 14.2997 16.1154 13.5281 16.4672 12.6788C16.8189 11.8295 17 10.9193 17 10C17 9.08075 16.8189 8.17050 16.4672 7.32122C16.1154 6.47194 15.5998 5.70026 14.9497 5.05025C14.2997 4.40024 13.5281 3.88463 12.6788 3.53284C11.8295 3.18106 10.9193 3 10 3C9.08075 3 8.17050 3.18106 7.32122 3.53284C6.47194 3.88463 5.70026 4.40024 5.05025 5.05025C4.40024 5.70026 3.88463 6.47194 3.53284 7.32122C3.18106 8.17050 3 9.08075 3 10Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
3590                        </svg>
3591                    </div>
3592                </form>
3593            </div>
3594        `;
3595        $panel.before(navHTML);
3596
3597        // Style the panel as a white card
3598        $panel.css({
3599            'background': '#fff',
3600            'border-radius': '8px',
3601            'padding': '24px'
3602        });
3603
3604        // Headline inside the card (full width)
3605        $panel.append(
3606            '<h3 style="width:100%; margin:0; font-weight:700;">' +
3607            parentCode + ' ' + currentName +
3608            '</h3>'
3609        );
3610
3611        // Children as plain text items — width handled by existing responsive CSS
3612        children.forEach(function (child) {
3613            var grandchildren = getFieldChildren(child.s_code);
3614            var isParent = grandchildren.length > 0;
3615            var cssClass = isParent ? 'category-parent' : 'category-filter';
3616
3617            var itemHTML =
3618                '<div class="catalog-filter-detail--box ' + cssClass + '"' +
3619                ' data-name="' + child.s_name + '"' +
3620                ' data-code="' + child.s_code + '"' +
3621                ' style="cursor:pointer; background:none; padding:10px 0; border-radius:0;">' +
3622                '<span><strong>' + child.s_code + '</strong> ' + child.s_name + '</span>' +
3623                '</div>';
3624            $panel.append(itemHTML);
3625        });
3626
3627        // --- Exhibitor list below the subcategory card ---
3628        // Collect all codes in this subtree (parentCode + all descendants)
3629        var allCategoryCodes = [parentCode];
3630        Object.keys(oboryFullData).forEach(function (key) {
3631            if (key === '') return;
3632            var code = oboryFullData[key].s_code;
3633            if (code.indexOf(parentCode) === 0 && code !== parentCode) {
3634                allCategoryCodes.push(code);
3635            }
3636        });
3637
3638        var exhibitorSectionHTML = `
3639            <div class="obory-exhibitors" style="display: flex; flex-direction: column; gap: 16px;">
3640                <h2 class="fields-headline">Vystavovatelé v oboru</h2>
3641                <div class="sort-buttons-wrapper desktop"><a href="#" class="alphabet-sort sort-button is-active w-inline-block"><div>Podle abecedy</div><div class="sort-arrows-wrapper"><div class="sort-icon is-active w-embed">
3641<svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a><a href="#" class="hall-sort sort-button w-inline-block"><div>Podle haly</div><div class="sort-arrows-wrapper"><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 6L5.5 1.5L10 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div><div class="sort-icon w-embed"><svg width="11" height="7" viewBox="0 0 11 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 1L5.5 5.5L1 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></div></div></a></div>
3642                <ul role="list" class="companies-list katalog obory-exhibitor-list">
3643                    <li class="companies-list-item"><div>Načítání...</div></li>
3644                </ul>
3645            </div>
3646        `;
3647        $panel.after(exhibitorSectionHTML);
3648
3649        var $exhibitorList = $panel.siblings('.obory-exhibitors').find('.obory-exhibitor-list');
3650        var $searchInput = $panel.siblings('.obory-nav').find('.obory-search-input');
3651
3652        $.ajax({
3653            url: '/wp-json/api/v1/oborove-cleneni',
3654            method: 'POST',
3655            data: {
3656                categories: allCategoryCodes,
3657                oboryLoadNeeded: false,
3658                catalog: currentCatalogSourceKey
3659            },
3660            success: function (response) {
3661                var dataSeznamVystavovatelu = response['seznam-vystavovatelu'];
3662                var isAscendingObory = true;
3663                var sortByObory = 'name';
3664                var $sortingButtons = $panel.siblings('.obory-exhibitors').find('.sort-buttons-wrapper');
3665
3666                $exhibitorList.empty();
3667                appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory);
3668
3669                // Sorting buttons
3670                $sortingButtons.find('.alphabet-sort').on('click', function () {
3671                    isAscendingObory = !isAscendingObory;
3672                    sortByObory = 'name';
3673                    updateSortIcons(isAscendingObory, $(this));
3674                    appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory);
3675                });
3676
3677                $sortingButtons.find('.hall-sort').on('click', function () {
3678                    isAscendingObory = !isAscendingObory;
3679                    sortByObory = 'hala';
3680                    updateSortIcons(isAscendingObory, $(this));
3681                    appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory);
3682                });
3683
3684                // Client-side search within fetched exhibitors
3685                $searchInput.on('input', debounce(function () {
3686                    var query = $(this).val().trim().toLowerCase();
3687                    if (!query) {
3688                        $exhibitorList.empty();
3689                        appendExhibitorListNew($exhibitorList, dataSeznamVystavovatelu, isAscendingObory, sortByObory);
3690                        return;
3691                    }
3692                    var filtered = {};
3693                    Object.keys(dataSeznamVystavovatelu).forEach(function (key) {
3694                        var name = safeDecodeURIComponent(dataSeznamVystavovatelu[key].s_nazev).replace(/\+/g, ' ').toLowerCase();
3695                        if (name.indexOf(query) !== -1) {
3696                            filtered[key] = dataSeznamVystavovatelu[key];
3697                        }
3698                    });
3699                    $exhibitorList.empty();
3700                    appendExhibitorListNew($exhibitorList, filtered, isAscendingObory, sortByObory);
3701                }, 300));
3702            },
3703            error: function () {
3704                $exhibitorList.empty();
3705                $exhibitorList.append('<li class="companies-list-item"><div>Chyba při načítání vystavovatelů.</div></li>');
3706            }
3707        });
3708    } else {
3709        // --- Root level: original box layout with icons ---
3710        children.forEach(function (child) {
3711            var grandchildren = getFieldChildren(child.s_code);
3712            var isParent = grandchildren.length > 0;
3713            var cssClass = isParent ? 'category-parent' : 'category-filter';
3714
3715            var boxHTML = `
3716                <div class="catalog-filter-detail--box ${cssClass}" data-name="${child.s_name}" data-code="${child.s_code}" style="cursor: pointer;">
3717                    <div class="img">
3718                         <img class="img" src="${customBlockData.pluginUrl}images/files.svg" />
3719                    </div>
3720                    <span class="list-item">${child.s_code} ${child.s_name}</span>
3721                </div>
3722            `;
3723            $panel.append(boxHTML);
3724        });
3725
3726        if (children.length === 0) {
3727            $panel.append('<div>Nebyly nalezeny žádné obory.</div>');
3728        }
3729    }
3730}
3731
3732function loadRSSfeeds() {
3733    $('.rss-feed-list-wrapper').each(function () {
3734        const $wrapper = $(this);
3735        const feedUrl = $wrapper.data('feed-url');
3736        const $output = $wrapper.find('.rss-feed-output');
3737        const $loading = $wrapper.find('.rss-feed-loading');
3738
3739        $.ajax({
3740            url: '/wp-json/api/v1/rss-feed/',
3741            method: 'GET',
3742            data: {
3743                feed: feedUrl
3744            },
3745            dataType: 'json',
3746            success: function (data) {
3747                $loading.remove();
3748
3749                if (!data.success) {
3750                    $output.html(`<div class="rss-error">❌ ${data.error}</div>`);
3751                    return;
3752                }
3753
3754                if (!data.data.length) {
3755                    $output.html(`<div class="rss-empty">Žádné články k zobrazení.</div>`);
3756                    return;
3757                }
3758
3759                data.data.forEach(function (item) {
3760                    const $link = $('<a>', {
3761                        href: item.link,
3762                        text: item.title,
3763                        class: 'rss-link',
3764                        target: '_blank'
3765                    });
3766
3767                    const $row = $('<div>', {
3768                        class: 'rss-feed-row'
3769                    }).append($link);
3770
3771                    $output.append($row);
3772                });
3773            },
3774            error: function (xhr, status, error) {
3775                $loading.remove();
3776                $output.html(`<div class="rss-error">❌ Chyba při načítání: ${error}</div>`);
3777            }
3778        });
3779    });
3780}

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.