PageSourceSearch

https://wineartshop.rs/js/app.js?v=t07g3P96y71VtvP7I9_aDHpvBEl-OQPLTLCnxOpv-BU

js wineartshop.rs collected 2026-09-24 14:27:20 UTC 42,756 bytes, 1,204 lines download raw bytes

1jQuery(document).ready(function () {
2    window.ShopAlert = window.Swal.mixin({
3        buttonsStyling: false,
4        heightAuto: false,
5        confirmButtonText: 'U redu',
6        cancelButtonText: 'Odustani',
7        customClass: {
8            container: 'shop-alert-container',
9            popup: 'shop-alert',
10            title: 'shop-alert__title',
11            htmlContainer: 'shop-alert__content',
12            actions: 'shop-alert__actions',
13            confirmButton: 'shop-alert__button shop-alert__button--primary',
14            denyButton: 'shop-alert__button shop-alert__button--secondary',
15            cancelButton: 'shop-alert__button shop-alert__button--secondary',
16            closeButton: 'shop-alert__close',
17            icon: 'shop-alert__icon',
18            timerProgressBar: 'shop-alert__timer'
19        }
20    });
21
22    window.ShopToast = window.ShopAlert.mixin({
23        position: 'top-end',
24        toast: true,
25        showConfirmButton: false,
26        timer: 2000,
27        timerProgressBar: true,
28        didOpen: (toast) => {
29            toast.addEventListener('mouseenter', window.Swal.stopTimer);
30            toast.addEventListener('mouseleave', window.Swal.resumeTimer);
31        }
32    });
33
34    const checkoutLoginForm = document.getElementById("checkout_login_form");
35    if (checkoutLoginForm) {
36        checkoutLoginForm.addEventListener("submit", (e) => {
37            e.preventDefault();
38            startLoading();
39            sendData(checkoutLoginForm);
40        });
41    }
42
43    const checkoutRegisterForm = document.getElementById("checkout_register_form");
44    if (checkoutRegisterForm) {
45        checkoutRegisterForm.addEventListener("submit", (e) => {
46            e.preventDefault();
47            startLoading();
48            sendData(checkoutRegisterForm);
49        });
50    }
51
52    const loginForm = document.getElementById("login_form");
53    if (loginForm) {
54        loginForm.addEventListener("submit", (e) => {
55            e.preventDefault();
56            startLoading();
57            sendData(loginForm);
58        });
59    }
60
61
62    const addressForm = document.getElementById("address_form");
63    if (addressForm) {
64        addressForm.addEventListener("submit", (e) => {
65            e.preventDefault();
66            startLoading();
67            sendData(addressForm);
68        });
69    }
70
71
72    const registerForm = document.getElementById("register_form");
73    if (registerForm) {
74        registerForm.addEventListener("submit", (e) => {
75            e.preventDefault();
76            startLoading();
77            sendData(registerForm);
78        });
79    }
80
81    const updateProfileForm = document.getElementById("profile_form");
82    if (updateProfileForm) {
83        updateProfileForm.addEventListener("submit", (e) => {
84            e.preventDefault();
85            startLoading();
86            sendData(updateProfileForm, false);
87        });
88    }
89
90
91    const newsletterForm = document.getElementById("newsletter_form");
92    if (newsletterForm) {
93        const submitButton = newsletterForm.querySelector("[data-newsletter-submit]");
94        const submitLabel = newsletterForm.querySelector("[data-newsletter-submit-label]");
95        const emailInput = newsletterForm.querySelector('input[name="Email"]');
96        const statusMessage = newsletterForm.querySelector("#newsletter-status");
97        const defaultSubmitLabel = submitLabel.textContent;
98
99        const setNewsletterStatus = (state, message) => {
100            newsletterForm.classList.remove("is-loading", "is-success", "is-error");
101
102            if (!state || !message) {
103                statusMessage.hidden = true;
104                statusMessage.textContent = "";
105                return;
106            }
107
108            newsletterForm.classList.add(`is-${state}`);
109            statusMessage.textContent = Array.isArray(message) ? message.join(" ") : message;
110            statusMessage.hidden = false;
111        };
112
113        emailInput.addEventListener("input", () => {
114            if (!newsletterForm.classList.contains("is-loading")) {
115                setNewsletterStatus(null, null);
116                submitButton.disabled = false;
117                submitLabel.textContent = defaultSubmitLabel;
118            }
119        });
120
121        newsletterForm.addEventListener("submit", async (e) => {
122            e.preventDefault();
123
124            if (!newsletterForm.checkValidity()) {
125                newsletterForm.reportValidity();
126                return;
127            }
128
129            submitButton.disabled = true;
130            submitLabel.textContent = "Prijavljujemo...";
131            setNewsletterStatus(null, null);
132            newsletterForm.classList.add("is-loading");
133            newsletterForm.setAttribute("aria-busy", "true");
134
135            try {
136                const response = await fetch(newsletterForm.action, {
137                    body: new FormData(newsletterForm),
138                    method: "POST",
139                    cache: "no-cache",
140                    credentials: "same-origin",
141                    headers: {
142                        "Accept": "application/json"
143                    }
144                });
145
146                const contentType = response.headers.get("content-type") || "";
147                if (!contentType.includes("application/json")) {
148                    throw new Error("Server trenutno nije dostupan. Molimo vas, pokušajte ponovo.");
149                }
150
151                const result = await response.json();
152                if (!response.ok || !result.success) {
153                    throw new Error(Array.isArray(result.message) ? result.message.join(" ") : result.message);
154                }
155
156                newsletterForm.reset();
157                submitLabel.textContent = "Prijavljeni ste";
158                setNewsletterStatus("success", result.message);
159            } catch (error) {
160                submitButton.disabled = false;
161                submitLabel.textContent = defaultSubmitLabel;
162                setNewsletterStatus("error", error.message || "Nešto nije u redu. Molimo vas, pokušajte ponovo.");
163            } finally {
164                newsletterForm.classList.remove("is-loading");
165                newsletterForm.removeAttribute("aria-busy");
166            }
167        });
168    }
169
170    const wishlistPage = document.querySelector("[data-wishlist-page]");
171    if (wishlistPage) {
172        const wishlistList = wishlistPage.querySelector("[data-wishlist-list]");
173        const wishlistEmpty = wishlistPage.querySelector("[data-wishlist-empty]");
174        const wishlistStatus = wishlistPage.querySelector("[data-wishlist-status]");
175        const wishlistCounts = wishlistPage.querySelectorAll("[data-wishlist-count]");
176        let statusTimeout;
177
178        const showWishlistStatus = (state, message) => {
179            window.clearTimeout(statusTimeout);
180            wishlistStatus.classList.remove("is-success", "is-error");
181            wishlistStatus.classList.add(`is-${state}`);
182            wishlistStatus.textContent = message;
183            wishlistStatus.hidden = false;
184
185            statusTimeout = window.setTimeout(() => {
186                wishlistStatus.hidden = true;
187            }, 5000);
188        };
189
190        const updateWishlistCount = () => {
191            const count = wishlistPage.querySelectorAll("[data-wishlist-item]").length;
192            wishlistCounts.forEach((element) => {
193                element.textContent = count;
194            });
195
196            if (count === 0) {
197                wishlistList.classList.add("d-none");
198                wishlistEmpty.classList.remove("d-none");
199            }
200        };
201
202        wishlistPage.addEventListener("click", async (event) => {
203            const removeButton = event.target.closest('[data-action="remove-from-wishlist"]');
204            if (!removeButton) {
205                return;
206            }
207
208            const wishlistItem = removeButton.closest("[data-wishlist-item]");
209            const wishlistId = removeButton.dataset.wishlistId;
210            if (!wishlistItem || !wishlistId || removeButton.disabled) {
211                return;
212            }
213
214            removeButton.disabled = true;
215            wishlistItem.classList.add("is-processing");
216            wishlistItem.setAttribute("aria-busy", "true");
217
218            try {
219                const response = await fetch("/wishlist/remove", {
220                    method: "POST",
221                    cache: "no-cache",
222                    credentials: "same-origin",
223                    headers: {
224                        "Accept": "application/json",
225                        "Content-Type": "application/json"
226                    },
227                    body: JSON.stringify({ id: wishlistId })
228                });
229
230                const contentType = response.headers.get("content-type") || "";
231                const result = contentType.includes("application/json")
232                    ? await response.json()
233                    : null;
234
235                if (!response.ok || !result?.success) {
236                    throw new Error(result?.message || "Proizvod trenutno nije moguće ukloniti.");
237                }
238
239                wishlistItem.classList.remove("is-processing");
240                wishlistItem.classList.add("is-removing");
241                await new Promise((resolve) => window.setTimeout(resolve, 180));
242                wishlistItem.remove();
243                updateWishlistCount();
244                showWishlistStatus("success", result.message);
245            } catch (error) {
246                removeButton.disabled = false;
247                wishlistItem.classList.remove("is-processing");
248                wishlistItem.removeAttribute("aria-busy");
249                showWishlistStatus("error", error.message || "Došlo je do greške. Molimo vas, pokušajte ponovo.");
250            }
251        });
252    }
253
254    const checkoutLoginWrapper = document.getElementById("checkout_login_form_wrapper");
255    const checkoutRegisterWrapper = document.getElementById("checkout_register_form_wrapper");
256    const showCreateAccountBtn = document.querySelector('[data-action="show_create_account"]');
257    if (showCreateAccountBtn) {
258        showCreateAccountBtn.addEventListener("click", (e) => {
259            e.preventDefault();
260            checkoutLoginWrapper.classList.add("d-none");
261            checkoutLoginWrapper.setAttribute("aria-hidden", "true");
262            checkoutRegisterWrapper.classList.remove("d-none");
263            checkoutRegisterWrapper.setAttribute("aria-hidden", "false");
264            showCreateAccountBtn.setAttribute("aria-expanded", "true");
265            checkoutRegisterWrapper.querySelector("h2")?.focus();
266        });
267    }
268
269    const showLoginBtn = document.querySelector('[data-action="show_login"]');
270    if (showLoginBtn) {
271        showLoginBtn.addEventListener("click", (e) => {
272            e.preventDefault();
273            checkoutRegisterWrapper.classList.add("d-none");
274            checkoutRegisterWrapper.setAttribute("aria-hidden", "true");
275            checkoutLoginWrapper.classList.remove("d-none");
276            checkoutLoginWrapper.setAttribute("aria-hidden", "false");
277            showCreateAccountBtn?.setAttribute("aria-expanded", "false");
278            checkoutLoginWrapper.querySelector("input[type='email']")?.focus();
279        });
280    }
281
282    const contactForm = document.getElementById("contact-form");
283    if (contactForm) {
284        contactForm.addEventListener("submit", function (e) {
285            e.preventDefault();
286            startLoading();
287            sendData(contactForm, true);
288        });
289    }
290
291
292});
293
294
295$('#sidebar_filter_form').on("submit",
296    (e) => {
297        e.preventDefault();
298        var form = document.getElementById("sidebar_filter_form");
299        var countries = $('[data-filter="country"]:checked').map((i, el) => el.value).get();
300        var types = $('[data-filter="type"]:checked').map((i, el) => el.value).get();
301        var varieties = $('[data-filter="variety"]:checked').map((i, el) => el.value).get();
302        var prices = $('[data-filter="price"]:checked').map((i, el) => el.value).get();
303        var styles = $('[data-filter="style"]:checked').map((i, el) => el.value).get();
304        var fruits = $('[data-filter="fruit"]:checked').map((i, el) => el.value).get();
305
306        var obj = {
307            countries: countries,
308            types: types,
309            varieties: varieties,
310            prices: prices,
311            styles: styles,
312            fruits: fruits
313        };
314
315        startLoading();
316        axios.post(form.action, obj).then(() => {
317            const url = new URL(window.location.href);
318            url.searchParams.delete('page');
319            window.location.href = url.toString();
320        }).catch(error => {
321            console.error('Error applying filters:', error);
322            stopLoading();
323        });
324    });
325
326$('#sidebar_filter_form_mobile').on("submit", (e) => {
327        e.preventDefault();
328        var form = document.getElementById("sidebar_filter_form_mobile");
329        var countries = $('[data-filter="mob-country"]:checked').map((i, el) => el.value).get();
330        var types = $('[data-filter="mob-type"]:checked').map((i, el) => el.value).get();
331        var varieties = $('[data-filter="mob-variety"]:checked').map((i, el) => el.value).get();
332        var prices = $('[data-filter="mob-price"]:checked').map((i, el) => el.value).get();
333        var styles = $('[data-filter="mob-style"]:checked').map((i, el) => el.value).get();
334        var fruits = $('[data-filter="mob-fruit"]:checked').map((i, el) => el.value).get();
335        const selectedSort = $('input[name="sortiranje"]:checked').val();
336
337        var obj = {
338            countries: countries,
339            types: types,
340            varieties: varieties,
341            prices: prices,
342            styles: styles,
343            fruits: fruits
344        };
345
346        startLoading();
347        axios.post(form.action, obj).then(() => {
348            const url = new URL(window.location.href);
349            if (selectedSort) {
350                url.searchParams.set('sort', selectedSort);
351            } else {
352                url.searchParams.delete('sort');
353            }
354            url.searchParams.delete('page');
355            window.location.href = url.toString();
356        }).catch(error => {
357            console.error('Error applying filters:', error);
358            stopLoading();
359        });
360    });
361
362
363
364$("[data-clear-filters]").on("click", (e) => {
365    e.preventDefault();
366    startLoading();
367    axios.post('/filters/clear').then(() => {
368        const url = new URL(window.location.href);
369        url.searchParams.delete('filter');
370        url.searchParams.delete('sort');
371        url.searchParams.delete('page');
372        window.location.href = url.toString();
373    }).catch(error => {
374        console.error('Error clearing filters:', error);
375        stopLoading();
376    });
377});
378
379$('[data-action="submit"]').on('click', function (e) {
380    e.preventDefault();
381    const form = e.target.closest('form');
382    startLoading();
383    sendData(form);
384});
385
386//SWIPER TO BE MOVED IN MAIN.JS
387
388var swiper = new Swiper(".topRatedSwiper", {
389    slidesPerView: 1,
390    spaceBetween: 30,
391    breakpoints: {
392        700: {
393            slidesPerView: 3,
394            spaceBetween: 20
395        },
396        1024: {
397            slidesPerView: 4,
398            spaceBetween: 40
399        }
400    },
401    navigation: {
402        nextEl: ".swiper-button-next",
403        prevEl: ".swiper-button-prev"
404    },
405     loop: true, // Enable looping
406    autoplay: {
407        delay: 3000, // Set delay to 3 seconds
408        disableOnInteraction: false // Continue autoplay after user interactions
409    }
410});
411
412var swiper1 = new Swiper(".mixMatchSwiper", {
413    slidesPerView: 1,
414    spaceBetween: 30,
415    breakpoints: {
416        700: {
417            slidesPerView: 3,
418            spaceBetween: 20
419        },
420        1024: {
421            slidesPerView: 4,
422            spaceBetween: 40
423        }
424    },
425    navigation: {
426        nextEl: ".swiper-button-next",
427        prevEl: ".swiper-button-prev"
428    },
429     loop: true, // Enable looping
430    autoplay: {
431        delay: 3000, // Set delay to 3 seconds
432        disableOnInteraction: false // Continue autoplay after user interactions
433    }
434});
435
436var swiper2 = new Swiper(".latestSwiper", {
437    slidesPerView: 1,
438    spaceBetween: 30,
439    breakpoints: {
440        700: {
441            slidesPerView: 3,
442            spaceBetween: 20
443        },
444        1024: {
445            slidesPerView: 4,
446            spaceBetween: 40
447        }
448    },
449    navigation: {
450        nextEl: ".swiper-button-prev",
451        prevEl: ".swiper-button-prev"
452    },
453    loop: true, // Enable looping
454    autoplay: {
455        delay: 3000, // Set delay to 3 seconds
456        disableOnInteraction: false // Continue autoplay after user interactions
457    }
458
459});
460
461var swiper3 = new Swiper(".bannerSwiper", {
462    slidesPerView: 1,
463    spaceBetween: 30,
464    breakpoints: {
465        700: {
466            slidesPerView: 1,
467            spaceBetween: 0
468        },
469        1024: {
470            slidesPerView: 1,
471            spaceBetween: 0
472        }
473    }
474});
475
476
477const removeFilter = (element) => {
478    const filterName = element.parentElement.getAttribute('data-filter-name');
479    const filterCategory = element.parentElement.getAttribute('data-filter-category');
480    const model = {
481        Name: filterName,
482        Category: filterCategory
483    };
484
485    element.disabled = true;
486    element.setAttribute('aria-busy', 'true');
487    startLoading();
488
489    $.ajax({
490        url: '/filters/remove',
491        type: 'POST',
492        contentType: 'application/json',
493        data: JSON.stringify(model),
494        success: function (response) {
495            if (response.success) {
496                const url = new URL(window.location.href);
497                url.searchParams.delete('page');
498                window.location.href = url.toString();
499            } else {
500                console.error('Failed to remove filter:', response.message);
501                element.disabled = false;
502                element.removeAttribute('aria-busy');
503                stopLoading();
504            }
505        },
506        error: function (error) {
507            console.error('Error removing filter:', error);
508            element.disabled = false;
509            element.removeAttribute('aria-busy');
510            stopLoading();
511        }
512    });
513};
514
515
516$(".js-submit").click(function (e) {
517    e.preventDefault();
518    const form = this.form;
519
520    if (!form || !form.checkValidity()) {
521        form?.reportValidity();
522        return;
523    }
524
525    startLoading();
526    sendData(form);
527});
528
529// Checkboxes that reveal a related block, e.g. the company fields on an address.
530document.addEventListener("change", function (e) {
531    const source = e.target.closest("[data-toggle-target]");
532    if (!source) {
533        return;
534    }
535
536    const target = document.querySelector(source.getAttribute("data-toggle-target"));
537    if (target) {
538        target.classList.toggle("d-none", !source.checked);
539    }
540});
541
542// Show/hide password on the account pages. Delegated so it also covers
543// any password field rendered after load.
544document.addEventListener("click", function (e) {
545    const toggle = e.target.closest("[data-password-toggle]");
546    if (!toggle) {
547        return;
548    }
549
550    const field = toggle.parentElement.querySelector("input");
551    if (!field) {
552        return;
553    }
554
555    const revealed = field.type === "text";
556    field.type = revealed ? "password" : "text";
557    const label = revealed ? "Prikaži lozinku" : "Sakrij lozinku";
558    const icon = toggle.querySelector(".fa-eye, .fa-eye-slash");
559
560    toggle.setAttribute("aria-label", label);
561    toggle.setAttribute("title", label);
562    icon?.classList.toggle("fa-eye", revealed);
563    icon?.classList.toggle("fa-eye-slash", !revealed);
564});
565
566const startLoading = (message = 'Samo trenutak…') => {
567    const loader = document.getElementById("css-loader");
568    if (!loader) return;
569    const title = loader.querySelector('[data-loading-title]');
570    if (title) title.textContent = message;
571    loader.setAttribute('aria-hidden', 'false');
572    loader.classList.add("is-active");
573}
574
575const stopLoading = () => {
576    const loader = document.getElementById("css-loader");
577    if (!loader) return;
578    loader.classList.remove("is-active");
579    loader.setAttribute('aria-hidden', 'true');
580}
581
582const sendData = (form, reset = true, token = null) => {
583    var formData = new FormData(form);
584
585    const resetTurnstile = () => {
586        if (form.querySelector('.cf-turnstile') && window.turnstile) {
587            window.turnstile.reset();
588        }
589    };
590
591    if (token !=  null) {
592        formData.append("Captcha", token);
593    }
594
595    fetch(form.action,
596        {
597            body: formData,
598            method: "post",
599            cache: 'no-cache'
600        }).then(response => response.json())
601        .then(result => {
602            resetTurnstile();
603
604            if (result.success && result.redirectUrl == null) {
605                if (reset) {
606                    form.reset();
607                }
608
609                const parentModal = form.closest(".modal");
610                if (parentModal && window.bootstrap?.Modal) {
611                    bootstrap.Modal.getOrCreateInstance(parentModal).hide();
612                }
613
614                stopLoading();
615                window.ShopAlert.fire({
616                    icon: 'success',
617                    title: 'Hvala Vam',
618                    text: result.message
619                });
620            } else if (result.success && result.redirectUrl) {
621                location.href = result.redirectUrl;
622            } else {
623                stopLoading();
624                window.ShopAlert.fire({
625                    icon: 'error',
626                    title: 'Greška!',
627                    text: result.message
628                });
629            }
630        }).catch(error => {
631            console.error('Error:', error);
632            resetTurnstile();
633            stopLoading();
634            window.ShopAlert.fire({
635                icon: 'error',
636                title: 'Greška',
637                text: 'Nešto nije u redu. Molimo vas, pokušajte ponovo.'
638            });
639        });
640}
641
642const wine_filters = document.querySelectorAll('[data-action="filter_changed"]');
643if (wine_filters.length > 0) {
644    wine_filters.forEach((el) => {
645        el.addEventListener('change',
646            (e) => {
647                const url = new URL(window.location.href);
648                const sort = e.currentTarget.value;
649
650                if (sort) {
651                    url.searchParams.set('sort', sort);
652                } else {
653                    url.searchParams.delete('sort');
654                }
655
656                url.searchParams.delete('page');
657                window.location.href = url.toString();
658            });
659    });
660}
661
662$(".modal-backdrop").click(e => {
663    e.stopPropagation();
664});
665
666
667const headerSearchCache = new Map();
668
669function createHeaderSearchResult(item, inputId, index) {
670    const result = document.createElement('li');
671    result.className = 'header-search__result';
672    result.id = `${inputId}-option-${index}`;
673    result.setAttribute('role', 'option');
674    result.setAttribute('aria-selected', 'false');
675
676    const link = document.createElement('a');
677    link.className = 'header-search__result-link';
678    link.href = item.uri || '#';
679    link.tabIndex = -1;
680
681    const imageWrapper = document.createElement('span');
682    imageWrapper.className = 'header-search__image';
683    if (item.imageUri) {
684        const image = document.createElement('img');
685        image.src = item.imageUri;
686        image.alt = '';
687        image.loading = 'lazy';
688        imageWrapper.appendChild(image);
689    }
690
691    const copy = document.createElement('span');
692    copy.className = 'header-search__copy';
693
694    const name = document.createElement('span');
695    name.className = 'header-search__name';
696    name.textContent = item.displayName;
697    copy.appendChild(name);
698
699    if (item.category) {
700        const category = document.createElement('span');
701        category.className = 'header-search__category';
702        category.textContent = item.category;
703        copy.appendChild(category);
704    }
705
706    link.appendChild(imageWrapper);
707    link.appendChild(copy);
708
709    if (item.priceText) {
710        const price = document.createElement('span');
711        price.className = 'header-search__price';
712        price.textContent = `${item.priceText} RSD`;
713        link.appendChild(price);
714    }
715
716    result.appendChild(link);
717    return result;
718}
719
720function initialiseHeaderSearch(form) {
721    const input = form.querySelector('[data-header-search-input]');
722    const clearButton = form.querySelector('[data-header-search-clear]');
723    const panel = form.querySelector('[data-header-search-panel]');
724    const resultsList = form.querySelector('[data-header-search-results]');
725    const loadingState = form.querySelector('[data-header-search-loading]');
726    const emptyState = form.querySelector('[data-header-search-empty]');
727    const allResultsLink = form.querySelector('[data-header-search-all]');
728    const allResultsLabel = form.querySelector('[data-header-search-all-label]');
729    const liveRegion = form.querySelector('[data-header-search-live]');
730    const suggestionsUrl = form.dataset.suggestionsUrl;
731    let debounceTimer;
732    let requestController;
733    let activeIndex = -1;
734
735    const getOptions = () => Array.from(resultsList.querySelectorAll('.header-search__result'));
736
737    const buildResultsUrl = (query) => {
738        const url = new URL(form.action, window.location.origin);
739        url.searchParams.set('q', query);
740        return url.toString();
741    };
742
743    const openPanel = () => {
744        panel.hidden = false;
745        input.setAttribute('aria-expanded', 'true');
746    };
747
748    const closePanel = () => {
749        panel.hidden = true;
750        input.setAttribute('aria-expanded', 'false');
751        input.removeAttribute('aria-activedescendant');
752        activeIndex = -1;
753        getOptions().forEach(option => {
754            option.classList.remove('is-active');
755            option.setAttribute('aria-selected', 'false');
756        });
757    };
758
759    const showLoading = () => {
760        resultsList.replaceChildren();
761        emptyState.hidden = true;
762        allResultsLink.hidden = true;
763        loadingState.hidden = false;
764        openPanel();
765    };
766
767    const updateActiveOption = (nextIndex) => {
768        const options = getOptions();
769        if (!options.length) return;
770
771        activeIndex = (nextIndex + options.length) % options.length;
772        options.forEach((option, index) => {
773            const isActive = index === activeIndex;
774            option.classList.toggle('is-active', isActive);
775            option.setAttribute('aria-selected', isActive ? 'true' : 'false');
776        });
777
778        const activeOption = options[activeIndex];
779        input.setAttribute('aria-activedescendant', activeOption.id);
780        activeOption.scrollIntoView({ block: 'nearest' });
781    };
782
783    const renderResults = (items, query) => {
784        loadingState.hidden = true;
785        resultsList.replaceChildren(...items.map((item, index) => createHeaderSearchResult(item, input.id, index)));
786        emptyState.hidden = items.length !== 0;
787        emptyState.textContent = 'Nema pronađenih proizvoda. Pokušajte sa drugim pojmom.';
788        allResultsLink.href = buildResultsUrl(query);
789        allResultsLabel.textContent = `Prikaži sve rezultate za „${query}”`;
790        allResultsLink.hidden = false;
791        activeIndex = -1;
792        input.removeAttribute('aria-activedescendant');
793        liveRegion.textContent = items.length
794            ? `${items.length} predloga je pronađeno.`
795            : 'Nema pronađenih predloga.';
796        openPanel();
797    };
798
799    const loadSuggestions = async (query) => {
800        const cacheKey = query.toLocaleLowerCase('sr');
801        if (headerSearchCache.has(cacheKey)) {
802            renderResults(headerSearchCache.get(cacheKey), query
802);
803            return;
804        }
805
806        requestController?.abort();
807        requestController = new AbortController();
808        showLoading();
809
810        try {
811            const url = new URL(suggestionsUrl, window.location.origin);
812            url.searchParams.set('q', query);
813            const response = await fetch(url, {
814                credentials: 'same-origin',
815                headers: { 'Accept': 'application/json' },
816                signal: requestController.signal
817            });
818
819            if (!response.ok) throw new Error(`Search request failed with ${response.status}`);
820
821            const data = await response.json();
822            const items = data.success && Array.isArray(data.items) ? data.items : [];
823            headerSearchCache.set(cacheKey, items);
824
825            if (input.value.trim() === query) {
826                renderResults(items, query);
827            }
828        } catch (error) {
829            if (error.name === 'AbortError') return;
830
831            loadingState.hidden = true;
832            resultsList.replaceChildren();
833            emptyState.textContent = 'Pretraga trenutno nije dostupna. Pokušajte ponovo.';
834            emptyState.hidden = false;
835            allResultsLink.hidden = true;
836            liveRegion.textContent = 'Pretraga trenutno nije dostupna.';
837            openPanel();
838        }
839    };
840
841    const handleInput = () => {
842        const query = input.value.trim();
843        clearButton.hidden = query.length === 0;
844        window.clearTimeout(debounceTimer);
845
846        if (query.length < 2) {
847            requestController?.abort();
848            resultsList.replaceChildren();
849            closePanel();
850            liveRegion.textContent = query.length === 1 ? 'Unesite još najmanje jedan karakter.' : '';
851            return;
852        }
853
854        debounceTimer = window.setTimeout(() => loadSuggestions(query), 250);
855    };
856
857    input.addEventListener('input', handleInput);
858    input.addEventListener('focus', () => {
859        if (input.value.trim().length >= 2 && (getOptions().length || !emptyState.hidden)) {
860            openPanel();
861        }
862    });
863
864    input.addEventListener('keydown', (event) => {
865        const options = getOptions();
866
867        if (event.key === 'ArrowDown' && options.length) {
868            event.preventDefault();
869            openPanel();
870            updateActiveOption(activeIndex + 1);
871        } else if (event.key === 'ArrowUp' && options.length) {
872            event.preventDefault();
873            openPanel();
874            updateActiveOption(activeIndex - 1);
875        } else if (event.key === 'Enter' && activeIndex >= 0 && options[activeIndex]) {
876            event.preventDefault();
877            window.location.assign(options[activeIndex].querySelector('a').href);
878        } else if (event.key === 'Escape') {
879            event.preventDefault();
880            closePanel();
881        }
882    });
883
884    clearButton.addEventListener('click', () => {
885        window.clearTimeout(debounceTimer);
886        requestController?.abort();
887        input.value = '';
888        clearButton.hidden = true;
889        resultsList.replaceChildren();
890        closePanel();
891        input.focus();
892        liveRegion.textContent = 'Pretraga je obrisana.';
893    });
894
895    form.addEventListener('submit', (event) => {
896        const query = input.value.trim();
897        if (query.length < 2) {
898            event.preventDefault();
899            loadingState.hidden = true;
900            resultsList.replaceChildren();
901            allResultsLink.hidden = true;
902            emptyState.textContent = 'Unesite najmanje 2 karaktera za pretragu.';
903            emptyState.hidden = false;
904            openPanel();
905            input.focus();
906            return;
907        }
908
909        input.value = query;
910        closePanel();
911        if (typeof startLoading === 'function') startLoading();
912    });
913
914    allResultsLink.addEventListener('click', () => {
915        if (typeof startLoading === 'function') startLoading();
916    });
917
918    resultsList.addEventListener('click', event => {
919        if (event.target.closest('a') && typeof startLoading === 'function') startLoading();
920    });
921
922    form.addEventListener('pointerdown', event => event.stopPropagation());
923    document.addEventListener('pointerdown', closePanel);
924
925    clearButton.hidden = input.value.trim().length === 0;
926}
927
928document.querySelectorAll('[data-header-search]').forEach(initialiseHeaderSearch);
929
930
931const url = new URL(window.location.href);
932const params = new URLSearchParams(url.search);
933if (params.has("r")) {
934    params.delete("r");
935
936    if (params.has("variety")) {
937        params.delete("variety");
938    }
939    if (params.has("typ")) {
940        params.delete("typ");
941    }
942    if (params.has("price")) {
943        params.delete("price");
944    }
945    if (params.has("style")) {
946        params.delete("style");
947    }
948    if (params.has("country")) {
949        params.delete("country");
950    }
951    if (params.has("fruit")) {
952        params.delete("fruit");
953    }
954    console.log(params);
955    let newUrl = window.location.pathname;
956    if (params.length > 0) {
957        newUrl = window.location.pathname + "?" + params;
958    }
959    window.history.pushState({}, document.title, newUrl);
960}
961
962
963window.onload = function () {
964    setTimeout(function () {
965        var showBanner = getCookie("_was_age_check");
966        if (showBanner == null || showBanner != "true") {
967            const ageCheckElement = document.getElementById("ageCheckModal");
968            if (!ageCheckElement) {
969                return;
970            }
971
972            const options = {
973                backdrop: "static",
974                keyboard: false
975            };
976            const ageCheck = new bootstrap.Modal(ageCheckElement, options);
977            ageCheck.show();
978
979            const confirmButton = ageCheckElement.querySelector("[data-age-confirm]");
980            if (confirmButton) {
981                confirmButton.addEventListener("click", function () {
982                    setCookie("_was_age_check", "true", 2);
983                }, { once: true });
984            }
985        }
986
987    }, 300);
988
989    setTimeout(function () {
990        var alcoholBanner = getCookie("_was_show_alc_banner");
991        if (alcoholBanner == null || alcoholBanner !== "true") {
992            var alkoholZabrana = new bootstrap.Offcanvas('#alkoholZabrana');
993            alkoholZabrana.show();
994
995            setCookie("_was_show_alc_banner", "true",2);
996        }
997    }, 4000);
998};
999
1000
1001function setCookie(name, value, days) {
1002    const date = new Date();
1003    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
1004    const expires = "expires=" + date.toUTCString();
1005    document.cookie = name + "=" + value + ";" + expires + ";path=/";
1006}
1007
1008function getCookie(name) {
1009    const nameEQ = name + "=";
1010    const ca = document.cookie.split(';');
1011    for (let i = 0; i < ca.length; i++) {
1012        let c = ca[i];
1013        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
1014        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
1015    }
1016    return null;
1017}
1018
1019document.addEventListener('DOMContentLoaded', function () {
1020    // TODO (GDPR): This UI currently stores preferences only; connect every optional
1021    // service to these categories before treating the stored choice as consent.
1022    // Also add accept-all/reject-all actions, consent versioning and a way to reopen
1023    // the settings and withdraw consent from every page.
1024    const cookieConsent = document.getElementById('cookie-consent');
1025    const acceptButton = document.getElementById('accept-cookies');
1026    const readMoreButton = document.getElementById('read-more');
1027    const cookieDetails = document.getElementById('cookie-details');
1028    const settingsLabel = readMoreButton?.querySelector('.cookie-consent__settings-label');
1029
1030        function checkCookieConsent() {
1031
1032        if (!cookieConsent) return;
1033
1034        if (getCookie('cookieConsent') !== 'accepted' && cookieConsent) {
1035            cookieConsent.style.display = 'block';
1036        } else {
1037            cookieConsent.style.display = 'none';
1038        }
1039    }
1040
1041    if (acceptButton) {
1042        acceptButton.addEventListener('click', function () {
1043            const cookieSettings = {
1044                neophodni: true,
1045                funkcionalni: document.getElementById('funkcionalni').checked,
1046                performance: document.getElementById('performance').checked,
1047                analitika: document.getElementById('analitika').checked,
1048                oglasavanje: document.getElementById('oglasavanje').checked
1049            };
1050            setCookie('cookieConsent', 'accepted', 7);
1051            setCookie('cookieSettings', JSON.stringify(cookieSettings), 7);
1052            cookieConsent.style.display = 'none';
1053        });
1054    }
1055
1056    if (readMoreButton) {
1057        readMoreButton.addEventListener('click', function () {
1058            const shouldShowDetails = cookieDetails.hidden;
1059
1060            cookieDetails.hidden = !shouldShowDetails;
1061            readMoreButton.setAttribute('aria-expanded', shouldShowDetails.toString());
1062
1063            if (settingsLabel) {
1064                settingsLabel.textContent = shouldShowDetails ? 'Sakrij postavke' : 'Podesi kolačiće';
1065            }
1066        });
1067    }
1068
1069    checkCookieConsent();
1070});
1071
1072document.addEventListener('DOMContentLoaded', function () {
1073    const mobileMenu = document.getElementById('mainMenu');
1074    if (!mobileMenu) return;
1075
1076    const categories = Array.from(mobileMenu.querySelectorAll('[data-mobile-menu-category]'));
1077
1078    categories.forEach(function (category) {
1079        category.addEventListener('toggle', function () {
1080            if (!category.open) return;
1081
1082            categories.forEach(function (otherCategory) {
1083                if (otherCategory !== category) {
1084                    otherCategory.open = false;
1085                }
1086            });
1087        });
1088
1089        const groups = Array.from(category.querySelectorAll('.mobile-menu__group'));
1090        groups.forEach(function (group) {
1091            group.addEventListener('toggle', function () {
1092                if (!group.open) return;
1093
1094                groups.forEach(function (otherGroup) {
1095                    if (otherGroup !== group) {
1096                        otherGroup.open = false;
1097                    }
1098                });
1099            });
1100        });
1101    });
1102
1103    mobileMenu.addEventListener('hidden.bs.offcanvas', function () {
1104        mobileMenu.querySelectorAll('details[open]').forEach(function (details) {
1105            details.open = false;
1106        });
1107    });
1108});
1109
1110document.addEventListener('DOMContentLoaded', function () {
1111    const directory = document.querySelector('[data-brands-directory]');
1112    if (!directory) return;
1113
1114    const searchInput = directory.querySelector('[data-brand-search]');
1115    const clearSearchButton = directory.querySelector('[data-brand-search-clear]');
1116    const resetButton = directory.querySelector('[data-brand-reset]');
1117    const resultCount = directory.querySelector('[data-brand-visible-count]');
1118    const resultLabel = directory.querySelector('[data-brand-result-label]');
1119    const emptyState = directory.querySelector('[data-brands-empty]');
1120    const cards = Array.from(directory.querySelectorAll('[data-brand-card]'));
1121    const letterButtons = Array.from(directory.querySelectorAll('[data-brand-letter]'));
1122
1123    if (!searchInput || cards.length === 0) return;
1124
1125    let activeLetter = 'all';
1126
1127    function normalise(value) {
1128        return (value || '')
1129            .toLocaleLowerCase('sr-Latn-RS')
1130            .normalize('NFD')
1131            .replace(/[\u0300-\u036f]/g, '')
1132            .trim();
1133    }
1134
1135    function updateDirectory() {
1136        const query = normalise(searchInput.value);
1137        let visibleCards = 0;
1138
1139        cards.forEach(function (card) {
1140            const matchesSearch = !query || normalise(card.dataset.brandName).includes(query);
1141            const matchesLetter = activeLetter === 'all' || card.dataset.brandInitial === activeLetter;
1142            const isVisible = matchesSearch && matchesLetter;
1143
1144            card.hidden = !isVisible;
1145            if (isVisible) visibleCards += 1;
1146        });
1147
1148        if (clearSearchButton) {
1149            clearSearchButton.hidden = searchInput.value.length === 0;
1150        }
1151
1152        if (resultCount) resultCount.textContent = visibleCards.toString();
1153        if (resultLabel) {
1154            resultLabel.textContent = visibleCards === 1 ? 'pronađen brend' : 'pronađenih brendova';
1155        }
1156        if (emptyState) emptyState.hidden = visibleCards !== 0;
1157    }
1158
1159    function selectLetter(selectedButton) {
1160        activeLetter = selectedButton.dataset.brandLetter || 'all';
1161
1162        letterButtons.forEach(function (button) {
1163            const isActive = button === selectedButton;
1164            button.classList.toggle('is-active', isActive);
1165            button.setAttribute('aria-pressed', isActive.toString());
1166        });
1167
1168        updateDirectory();
1169    }
1170
1171    searchInput.addEventListener('input', updateDirectory);
1172
1173    if (clearSearchButton) {
1174        clearSearchButton.addEventListener('click', function () {
1175            searchInput.value = '';
1176            updateDirectory();
1177            searchInput.focus();
1178        });
1179    }
1180
1181    letterButtons.forEach(function (button) {
1182        button.addEventListener('click', function () {
1183            selectLetter(button);
1184        });
1185    });
1186
1187    if (resetButton) {
1188        resetButton.addEventListener('click', function () {
1189            searchInput.value = '';
1190            const allButton = letterButtons.find(function (button) {
1191                return button.dataset.brandLetter === 'all';
1192            });
1193
1194            if (allButton) {
1195                selectLetter(allButton);
1196            } else {
1197                activeLetter = 'all';
1198                updateDirectory();
1199            }
1200
1201            searchInput.focus();
1202        });
1203    }
1204});

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.