PageSourceSearch

https://grad-vinkovci.hr/js/pristupacnost.js?v=1789680251

js grad-vinkovci.hr collected 2026-10-02 20:47:31 UTC 33,793 bytes, 766 lines download raw bytes

1// ============================================================
2// Grad Vinkovci – pristupačnost + cookie privola
3// Postavke se automatski spremaju u sessionStorage i cookie.
4// ============================================================
5
6(function () {
7    "use strict";
8
9    var COOKIE_A11Y = "vk_a11y";
10    var COOKIE_SETTINGS = "vk_cookie_settings";
11    var COOKIE_LEGACY = "vk_cookie_consent";
12    var COOKIE_CAT_OPEN = "vk_cookie_cat_open";
13    var SESSION_KEY = "vk_a11y";
14    var SLIDER_MIN = 80;
15    var SLIDER_MAX = 150;
16    var SLIDER_STEP = 10;
17    var BASE_URL = window.VK_BASE_URL || "";
18    var PRIVACY_URL = (BASE_URL || "") + "/privatnost";
19
20    var MODE_CLASSES = [
21        "a11y-invert",
22        "a11y-mono",
23        "a11y-dark-contrast",
24        "a11y-light-contrast",
25        "a11y-low-sat",
26        "a11y-high-sat"
27    ];
28
29    var MODE_CLASS_MAP = {
30        invert: "a11y-invert",
31        mono: "a11y-mono",
32        darkContrast: "a11y-dark-contrast",
33        lightContrast: "a11y-light-contrast",
34        lowSat: "a11y-low-sat",
35        highSat: "a11y-high-sat"
36    };
37
38    var defaults = {
39        mode: null,
40        highlightLinks: false,
41        highlightHeadings: false,
42        contentScale: 100,
43        fontSize: 100,
44        lineHeight: 100,
45        letterSpacing: 100
46    };
47
48    var defaultCategories = {
49        system: true,
50        preferences: false,
51        analytics: false
52    };
53
54    var consentState = {
55        consentId: null,
56        consentedAt: null,
57        categories: Object.assign({}, defaultCategories)
58    };
59
60    var cookieCategories = [
61        {
62            key: "system",
63            title: "Sistemski kolačići (obavezni)",
64            desc: "Nužni za osnovno funkcioniranje web stranice (sigurnost, sesija, postavke suglasnosti).",
65            locked: true
66        },
67        {
68            key: "preferences",
69            title: "Kolačići postavki",
70            desc: "Pamte vaše postavke pristupačnosti (veličina fonta, kontrast i sl.).",
71            locked: false
72        },
73        {
74            key: "analytics",
75            title: "Analitički kolačići",
76            desc: "Pomažu nam razumjeti korištenje stranice putem anonimizirane statistike posjeta.",
77            locked: false
78        }
79    ];
80
81    var state = Object.assign({}, defaults);
82
83    function clampSlider(value) {
84        var n = parseInt(value, 10);
85        if (isNaN(n)) return 100;
86        n = Math.round(n / SLIDER_STEP) * SLIDER_STEP;
87        return Math.min(SLIDER_MAX, Math.max(SLIDER_MIN, n));
88    }
89
90    function setCookie(name, value, days) {
91        var expires = "";
92        if (days) {
93            var d = new Date();
94            d.setTime(d.getTime() + days * 864e5);
95            expires = "; expires=" + d.toUTCString();
96        }
97        document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/; SameSite=Lax";
98    }
99
100    function getCookie(name) {
101        var match = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)"));
102        return match ? decodeURIComponent(match[1]) : null;
103    }
104
105    function deleteCookie(name) {
106        document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax";
107    }
108
109    function loadOpenCategories() {
110        try {
111            var raw = localStorage.getItem(COOKIE_CAT_OPEN);
112            if (!raw) return [];
113            var list = JSON.parse(raw);
114            if (!Array.isArray(list)) return [];
115            var validKeys = cookieCategories.map(function (cat) { return cat.key; });
116            return list.filter(function (key) { return validKeys.indexOf(key) !== -1; });
117        } catch (e) {
118            return [];
119        }
120    }
121
122    function saveOpenCategories(list) {
123        try {
124            localStorage.setItem(COOKIE_CAT_OPEN, JSON.stringify(list));
125        } catch (e) { /* quota ili privatni način */ }
126    }
127
128    function generateUuid() {
129        if (window.crypto && typeof window.crypto.randomUUID === "function") {
130            return window.crypto.randomUUID();
131        }
132        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
133            var r = Math.random() * 16 | 0;
134            var v = c === "x" ? r : (r & 0x3 | 0x8);
135            return v.toString(16);
136        });
137    }
138
139    function hasPreferencesConsent() {
140        return !!consentState.categories.preferences;
141    }
142
143    function migrateLegacyConsent() {
144        var legacy = getCookie(COOKIE_LEGACY);
145        if (!legacy) return null;
146        deleteCookie(COOKIE_LEGACY);
147        if (legacy === "accepted") {
148            return {
149                consentId: generateUuid(),
150                consentedAt: new Date().toISOString(),
151                categories: { system: true, preferences: true, analytics: true }
152            };
153        }
154        if (legacy === "declined") {
155            return {
156                consentId: generateUuid(),
157                consentedAt: new Date().toISOString(),
158                categories: { system: true, preferences: false, analytics: fal
158se }
159            };
160        }
161        return null;
162    }
163
164    function loadConsentSettings() {
165        var raw = getCookie(COOKIE_SETTINGS);
166        if (raw) {
167            try {
168                var data = JSON.parse(raw);
169                if (data && data.consentId && data.categories) {
170                    consentState.consentId = data.consentId;
171                    consentState.consentedAt = data.consentedAt || null;
172                    consentState.categories = {
173                        system: true,
174                        preferences: !!data.categories.preferences,
175                        analytics: !!data.categories.analytics
176                    };
177                    return;
178                }
179            } catch (e) { /* neispravan zapis */ }
180        }
181        var migrated = migrateLegacyConsent();
182        if (migrated) {
183            consentState.consentId = migrated.consentId;
184            consentState.consentedAt = migrated.consentedAt;
185            consentState.categories = migrated.categories;
186            saveConsentSettings(false);
187        }
188    }
189
190    function saveConsentSettings(logServer) {
191        if (!consentState.consentId) {
192            consentState.consentId = generateUuid();
193        }
194        if (!consentState.consentedAt) {
195            consentState.consentedAt = new Date().toISOString();
196        }
197        setCookie(COOKIE_SETTINGS, JSON.stringify({
198            consentId: consentState.consentId,
199            consentedAt: consentState.consentedAt,
200            categories: consentState.categories
201        }), 365);
202        applyConsentEffects();
203        if (logServer !== false) {
204            syncCookieMetaUI();
205        }
206    }
207
208    function applyConsentEffects() {
209        if (!hasPreferencesConsent()) {
210            sessionStorage.removeItem(SESSION_KEY);
211            deleteCookie(COOKIE_A11Y);
212            state = Object.assign({}, defaults);
213            apply();
214            syncUI();
215        }
216    }
217
218    function logConsentToServer(action, categories) {
219        if (!consentState.consentId) return;
220        var url = (BASE_URL || "") + "/consent_log.php";
221        fetch(url, {
222            method: "POST",
223            headers: { "Content-Type": "application/json" },
224            body: JSON.stringify({
225                consent_id: consentState.consentId,
226                action: action,
227                categories: categories
228            }),
229            keepalive: true
230        }).catch(function () { /* ne blokira UI */ });
231    }
232
233    function formatConsentDate(iso) {
234        if (!iso) return "–";
235        var d = new Date(iso);
236        if (isNaN(d.getTime())) return "–";
237        var pad = function (n) { return n < 10 ? "0" + n : String(n); };
238        return pad(d.getDate()) + ". " + pad(d.getMonth() + 1) + ". " + d.getFullYear() + ". "
239            + pad(d.getHours()) + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
240    }
241
242    function hasConsentChoice() {
243        return !!getCookie(COOKIE_SETTINGS) || !!consentState.consentId;
244    }
245
246    function migrateLegacy(data) {
247        if (data.mode !== undefined || data.contentScale !== undefined) {
248            return {
249                mode: data.mode || null,
250                highlightLinks: !!data.highlightLinks,
251                highlightHeadings: !!data.highlightHeadings,
252                contentScale: clampSlider(data.contentScale),
253                fontSize: clampSlider(data.fontSize),
254                lineHeight: clampSlider(data.lineHeight),
255                letterSpacing: clampSlider(data.letterSpacing)
256            };
257        }
258
259        var result = Object.assign({}, defaults);
260        if (data.contrast) result.mode = "darkContrast";
261        if (data.underline) result.highlightLinks = true;
262        if (typeof data.fontStep === "number") {
263            result.fontSize = clampSlider(100 + data.fontStep * 10);
264        }
265        return result;
266    }
267
268    function loadA11y() {
269        if (!hasPreferencesConsent()) {
270            return Object.assign({}, defaults);
271        }
272        var raw = getCookie(COOKIE_A11Y) || sessionStorage.getItem(SESSION_KEY);
273        if (raw) {
274            try {
275                return migrateLegacy(JSON.parse(raw));
276            } catch (e) { /* neispravan zapis */ }
277        }
278        return Object.assign({}, defaults);
279    }
280
281    function persist() {
282        if (!hasPreferencesConsent()) return;
283        sessionStorage.setItem(SESSION_KEY, JSON.stringify(state));
284        setCookie(COOKIE_A11Y, JSON.stringify(state), 365);
285    }
286
287    function resetAll() {
288        state = Object.assign({}, defaults);
289        sessionStorage.removeItem(SESSION_KEY);
290        deleteCookie(COOKIE_A11Y);
291        apply();
292        syncUI();
293    }
294
295    function wrapContentRoot() {
296        if (document.getElementById("a11y-root")) return;
297
298        var root = document.createElement("div");
299        root.id = "a11y-root";
300        var toMove = [];
301
302        Array.prototype.forEach.call(document.body.children, function (el) {
303            if (el.classList.contains("a11y-fab") ||
304                el.classList.contains("cookie-fab") ||
305                el.classList.contains("a11y-overlay") ||
306                el.classList.contains("cookie-overlay") ||
307                el.classList.contains("cookie-banner") ||
308                el.classList.contains("nav-backdrop")) {
309                return;
310            }
311            toMove.push(el);
312        });
313
314        document.body.insertBefore(root, document.body.firstChild);
315        toMove.forEach(function (el) {
316            root.appendChild(el);
317        });
318    }
319
320    function apply() {
321        var root = document.getElementById("a11y-root");
322        if (!root) return;
323
324        var html = document.documentElement;
325        html.style.fontSize = state.fontSize === 100 ? "" : state.fontSize + "%";
326
327        root.style.zoom = state.contentScale === 100 ? "" : String(state.contentScale / 100);
328        root.style.lineHeight = state.lineHeight === 100 ? "" : String(1.6 * state.lineHeight / 100);
329        root.style.letterSpacing = state.letterSpacing === 100
330            ? ""
331            : ((state.letterSpacing - 100) * 0.01) + "em";
332
333        MODE_CLASSES.forEach(function (cls) {
334            root.classList.remove(cls);
335        });
336
337        if (state.mode && MODE_CLASS_MAP[state.mode]) {
338            root.classList.add(MODE_CLASS_MAP[state.mode]);
339        }
340
341        root.classList.toggle("a11y-highlight-links", state.highlightLinks);
342        root.classList.toggle("a11y-highlight-headings", state.highlightHeadings);
343    }
344
345    function updateState() {
346        apply();
347        syncUI();
348        persist();
349    }
350
351    var icons = {
352        invert: '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 3v18" fill="currentColor" stroke="none"/></svg>',
353        mono: '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M4 20L20 4"/></svg>',
354        darkContrast: '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 14A9 9 0 1114 3a7 7 0 007 11z"/></svg>',
355        lightContrast: '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>',
356        lowSat: '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 010 18" fill="currentColor" stroke="none" opacity="0.35"/></svg>',
357        highSat: '<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="12" cy="12" r="9"/></svg>',
358        highlightLinks: '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>',
359        highlightHeadings: '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9h4M7 13h7M7 17h5"/></svg>',
360        reset: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 12a9 9 0 109-9"/><path d="M3 4v5h5"/></svg>',
361        person: '<svg width="26" height="26" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="4.5" r="2.2"/><path d="M12 8c-3 0-5.5-.6-5.5-.6a.9.9 0 00-.5 1.7s2.4.7 4.2.9c0 0 .1 1.6-.3 3.1-.4 1.6-1.7 4.6-1.7 4.6a.95.95 0 001.7.8s1.4-3 2.1-4.6c.7 1.6 2.1 4.6 2.1 4.6a.95.95 0 001.7-.8s-1.3-3-1.7-4.6c-.4-1.5-.3-3.1-.3-3.1 1.8-.2 4.2-.9 4.2-.9a.9.9 0 00-.5-1.7S15 8 12 8z"/></svg>',
362        cookie: '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.25 2 11.5 2 16.75 6.48 21 12 21c5.52 0 10-4.25 10-9.5S17.52 2 12 2zm0 2c.55 0 1.08.05 1.58.14-.36.62-.58 1.34-.58 2.11 0 2.21 1.79 4 4 4 .47 0 .92-.08 1.34-.23.64 1.05 1.08 2.24 1.25 3.48C17.9 17.2 15.12 19 12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7z"/><circle cx="8.5" cy="11" r="1.2" fill="#fff"/><circle cx="12.5" cy="8.5" r="1" fill="#fff"/><circle cx="15" cy="12.5" r="1.1" fill="#fff"/><circle cx="10" cy="15" r=".9" fill="#fff"/></svg>'
363    };
364
365    var tiles = [
366        { mode: "invert", label: "Invertiraj boje", icon: icons.invert },
367        { mode: "mono", label: "Monokromatski", icon: icons.mono },
368        { mode: "darkContrast", label: "Tamni kontrast", icon: icons.darkContrast },
369        { mode: "lightContrast", label: "Svijetli kontrast", icon: icons.lightContrast },
370        { mode: "lowSat", label: "Niska zasićenost", icon: icons.lowSat },
371        { mode: "highSat", label: "Visoka zasićenost", icon: icons.highSat },
372        { toggle: "highlightLinks", label: "Istakni veze", icon: icons.highlightLinks },
373        { toggle: "highlightHeadings", label: "Istakni naslove", icon: icons.highlightHeadings }
374    ];
375
376    var sliders = [
377        { key: "contentScale", label: "Skaliranje sadržaja" },
378        { key: "fontSize", label: "Veličina fonta" },
379        { key: "lineHeight", label: "Visina retka" },
380        { key: "letterSpacing", label: "Prored između slova" }
381    ];
382
383    function buildTileHtml(tile) {
384        var attrs = tile.mode
385            ? ' data-mode="' + tile.mode + '"'
386            : ' data-toggle="' + tile.toggle + '"';
387        return '<button type="button" class="a11y-tile"' + attrs + ' aria-pressed="false">' +
388            '<span class="a11y-tile__icon">' + tile.icon + '</span>' +
389            '<span class="a11y-tile__label">' + tile.label + '</span>' +
390            '</button>';
391    }
392
393    function buildSliderHtml(slider) {
394        return '<div class="a11y-slider-row" data-key="' + slider.key + '">' +
395            '<span class="a11y-slider-row__label">' + slider.label + '</span>' +
396            '<input type="range" class="a11y-slider" min="' + SLIDER_MIN + '" max="' + SLIDER_MAX + '" step="' + SLIDER_STEP + '" value="100" aria-label="' + slider.label + '">' +
397            '<span class="a11y-slider-row__value">100%</span>' +
398            '<div class="a11y-slider-row__steppers">' +
399                '<button type="button" class="a11y-stepper" data-dir="-1" aria-label="Smanji ' + slider.label.toLowerCase() + '">&minus;</button>' +
400                '<button type="button" class="a11y-stepper" data-dir="1" aria-label="Povećaj ' + slider.label.toLowerCase() + '">+</button>' +
401            '</div>' +
402        '</div>';
403    }
404
405    function buildCategoryHtml(cat) {
406        var checked = consentState.categories[cat.key] ? " checked" : "";
407        var disabled = cat.locked ? " disabled" : "";
408        var isOpen = loadOpenCategories().indexOf(cat.key) !== -1;
409        return '<div class="cookie-category' + (cat.locked ? " cookie-category--locked" : "") + '" data-key="' + cat.key + '">' +
410            '<div class="cookie-category__head">' +
411                '<button type="button" class="cookie-category__toggle" aria-expanded="' + (isOpen ? "true" : "false") + '" aria-controls="cookieCat-' + cat.key + '">' +
412                    '<span class="cookie-category__title">' + cat.title + '</span>' +
413                    '<span class="cookie-category__chevron" aria-hidden="true">&#9662;</span>' +
414                '</button>' +
415                '<label class="cookie-switch">' +
416                    '<input type="checkbox" class="cookie-switch__input" data-cat="' + cat.key + '"' + checked + disabled + '>' +
417                    '<span class="cookie-switch__track"></span>' +
418                '</label>' +
419            '</div>' +
420            '<div class="cookie-category__body" id="cookieCat-' + cat.key + '"' + (isOpen ? "" : " hidden") + '>
420' +
421                '<p>' + cat.desc + '</p>' +
422            '</div>' +
423        '</div>';
424    }
425
426    function buildCookieModal() {
427        var categoriesHtml = cookieCategories.map(buildCategoryHtml).join("");
428        var wrap = document.createElement("div");
429        wrap.innerHTML =
430            '<div class="cookie-overlay" id="cookieOverlay" role="dialog" aria-modal="true" aria-labelledby="cookieTitle" hidden>' +
431                '<div class="cookie-modal">' +
432                    '<div class="cookie-modal__head">' +
433                        '<h2 id="cookieTitle">Postavke kolačića</h2>' +
434                        '<button type="button" class="cookie-modal__close" id="cookieClose" aria-label="Zatvori">&times;</button>' +
435                    '</div>' +
436                    '<div class="cookie-modal__body">' +
437                        '<p class="cookie-modal__intro">Korištenje kolačića. Koristimo kolačiće kako bismo osigurali osnovne funkcionalnosti web stranice i poboljšali vaše online iskustvo. Za određene kategorije možete odabrati da se uključite/isključite kad god želite. Više informacija u <a href="' + PRIVACY_URL + '" class="cookie-banner__link">Politici privatnosti</a>.</p>' +
438                        '<div class="cookie-categories">' + categoriesHtml + '</div>' +
439                        '<div class="cookie-meta" id="cookieMeta">' +
440                            '<div><strong>Suglasnost dana:</strong> <span id="cookieMetaDate">–</span></div>' +
441                            '<div><strong>ID suglasnosti:</strong> <span id="cookieMetaId">–</span></div>' +
442                        '</div>' +
443                    '</div>' +
444                    '<div class="cookie-modal__foot">' +
445                        '<button type="button" class="cookie-btn cookie-btn--primary" id="cookieAcceptAll">Prihvati sve</button>' +
446                        '<button type="button" class="cookie-btn cookie-btn--secondary" id="cookieRejectAll">Odbij sve</button>' +
447                        '<button type="button" class="cookie-btn cookie-btn--secondary" id="cookieSave">Spremi postavke</button>' +
448                    '</div>' +
449                '</div>' +
450            '</div>';
451        while (wrap.firstChild) document.body.appendChild(wrap.firstChild);
452    }
453
454    function buildCookieFab() {
455        var fab = document.createElement("button");
456        fab.type = "button";
457        fab.className = "cookie-fab";
458        fab.id = "cookieFab";
459        fab.setAttribute("aria-label", "Postavke kolačića");
460        fab.setAttribute("title", "Postavke kolačića");
461        fab.innerHTML = icons.cookie;
462        document.body.appendChild(fab);
463    }
464
465    function buildCookieBanner() {
466        if (hasConsentChoice()) return;
467        var banner = document.createElement("div");
468        banner.className = "cookie-banner";
469        banner.id = "cookieBanner";
470        banner.setAttribute("role", "region");
471        banner.setAttribute("aria-label", "Obavijest o kolačićima");
472        banner.innerHTML =
473            '<div class="cookie-banner__inner">' +
474                '<p>Ova web stranica koristi kolačiće kako bi osigurala bolje korisničko iskustvo i ispravno funkcioniranje stranice. Možete odabrati koje kategorije kolačića prihvaćate. <a href="' + PRIVACY_URL + '" class="cookie-banner__link">Politika privatnosti</a>. <button type="button" class="cookie-banner__link" id="cookieBannerSettings">Postavke kolačića</button></p>' +
475                '<div class="cookie-banner__actions">' +
476                    '<button type="button" class="cookie-banner__btn cookie-banner__btn--accept" id="cookieBannerAccept">Prihvati sve</button>' +
477                    '<button type="button" class="cookie-banner__btn cookie-banner__btn--decline" id="cookieBannerDecline">Odbij sve</button>' +
478                '</div>' +
479            '</div>';
480        document.body.appendChild(banner);
481        document.body.classList.add("cookie-banner-visible");
482
483        document.getElementById("cookieBannerSettings").addEventListener("click", function () {
484            openCookieModal();
485        });
486        document.getElementById("cookieBannerAccept").addEventListener("click", function () {
487            acceptAllCookies();
488            document.body.classList.remove("cookie-banner-visible");
489            banner.remove();
490        });
491        document.getElementById("cookieBannerDecline").addEventListener("click", function () {
492            rejectAllCookies();
493            document.body.classList.remove("cookie-banner-visible");
494            banner.remove();
495        });
496    }
497
498    function buildWidget() {
499        var gridHtml = tiles.map(buildTileHtml).join("");
500        var slidersHtml = sliders.map(buildSliderHtml).join("");
501
502        var wrap = document.createElement("div");
503        wrap.innerHTML =
504            '<button type="button" class="a11y-fab" id="a11yFab" aria-label="Pristupačnost" title="Pristupačnost">' + icons.person + '</button>' +
505
506            '<div class="a11y-overlay" id="a11yOverlay" role="dialog" aria-modal="true" aria-labelledby="a11yTitle" hidden>' +
507                '<div class="a11y-modal">' +
508                    '<div class="a11y-modal__head">' +
509                        '<h2 id="a11yTitle">Alati za pristupačnost</h2>' +
510                        '<div class="a11y-modal__actions">' +
511                            '<button type="button" class="a11y-modal__reset" id="a11yReset" aria-label="Resetiraj postavke">' + icons.reset + '</button>' +
512                            '<button type="button" class="a11y-modal__close" id="a11yClose" aria-label="Zatvori">
512&times;</button>' +
513                        '</div>' +
514                    '</div>' +
515                    '<div class="a11y-modal__body">' +
516                        '<div class="a11y-grid">' + gridHtml + '</div>' +
517                        '<div class="a11y-sliders">' + slidersHtml + '</div>' +
518                    '</div>' +
519                '</div>' +
520            '</div>';
521
522        while (wrap.firstChild) document.body.appendChild(wrap.firstChild);
523    }
524
525    function syncCookieMetaUI() {
526        var dateEl = document.getElementById("cookieMetaDate");
527        var idEl = document.getElementById("cookieMetaId");
528        if (dateEl) dateEl.textContent = formatConsentDate(consentState.consentedAt);
529        if (idEl) idEl.textContent = consentState.consentId || "–";
530    }
531
532    function syncCookieTogglesUI() {
533        document.querySelectorAll(".cookie-switch__input").forEach(function (input) {
534            var key = input.dataset.cat;
535            if (!key || key === "system") return;
536            input.checked = !!consentState.categories[key];
537        });
538    }
539
540    function openCookieModal() {
541        var overlay = document.getElementById("cookieOverlay");
542        if (!overlay) return;
543        syncCookieTogglesUI();
544        syncCookieMetaUI();
545        overlay.hidden = false;
546        document.body.classList.add("cookie-modal-open");
547        document.getElementById("cookieClose").focus();
548    }
549
550    function closeCookieModal() {
551        var overlay = document.getElementById("cookieOverlay");
552        if (!overlay) return;
553        overlay.hidden = true;
554        document.body.classList.remove("cookie-modal-open");
555        var fab = document.getElementById("cookieFab");
556        if (fab) fab.focus();
557    }
558
559    function readTogglesFromUI() {
560        document.querySelectorAll(".cookie-switch__input").forEach(function (input) {
561            var key = input.dataset.cat;
562            if (!key || key === "system") return;
563            consentState.categories[key] = input.checked;
564        });
565        consentState.categories.system = true;
566    }
567
568    function acceptAllCookies() {
569        consentState.categories = { system: true, preferences: true, analytics: true };
570        consentState.consentedAt = new Date().toISOString();
571        if (!consentState.consentId) consentState.consentId = generateUuid();
572        saveConsentSettings(false);
573        logConsentToServer("accept_all", consentState.categories);
574        syncCookieTogglesUI();
575        syncCookieMetaUI();
576        closeCookieModal();
577        var banner = document.getElementById("cookieBanner");
578        if (banner) banner.remove();
579        state = loadA11y();
580        apply();
581        syncUI();
582    }
583
584    function rejectAllCookies() {
585        consentState.categories = { system: true, preferences: false, analytics: false };
586        consentState.consentedAt = new Date().toISOString();
587        if (!consentState.consentId) consentState.consentId = generateUuid();
588        saveConsentSettings(false);
589        logConsentToServer("reject_all", consentState.categories);
590        syncCookieTogglesUI();
591        syncCookieMetaUI();
592        closeCookieModal();
593        var banner = document.getElementById("cookieBanner");
594        if (banner) banner.remove();
595    }
596
597    function saveCookieSettings() {
598        readTogglesFromUI();
599        consentState.consentedAt = new Date().toISOString();
600        if (!consentState.consentId) consentState.consentId = generateUuid();
601        saveConsentSettings(false);
602        logConsentToServer("save_settings", consentState.categories);
603        syncCookieMetaUI();
604        closeCookieModal();
605        var banner = document.getElementById("cookieBanner");
606        if (banner) banner.remove();
607        if (hasPreferencesConsent()) {
608            state = loadA11y();
609        } else {
610            state = Object.assign({}, defaults);
611        }
612        apply();
613        syncUI();
614    }
615
616    function initCookieUI() {
617        buildCookieModal();
618        buildCookieFab();
619        buildCookieBanner();
620        syncCookieMetaUI();
621
622        var overlay = document.getElementById("cookieOverlay");
623        var closeBtn = document.getElementById("cookieClose");
624        var fab = document.getElementById("cookieFab");
625
626        fab.addEventListener("click", openCookieModal);
627        closeBtn.addEventListener("click", closeCookieModal);
628
629        overlay.addEventListener("click", function (e) {
630            if (e.target === overlay) closeCookieModal();
631        });
632
633        document.getElementById("cookieAcceptAll").addEventListener("click", acceptAllCookies);
634        document.getElementById("cookieRejectAll").addEventListener("click", rejectAllCookies);
635        document.getElementById("cookieSave").addEventListener("click", saveCookieSettings);
636
637        overlay.querySelectorAll(".cookie-category__toggle").forEach(function (btn) {
638            btn.addEventListener("click", function () {
639                var category = btn.closest(".cookie-category");
640                var key = category ? category.getAttribute("data-key") : null;
641                var body = btn.parentElement.nextElementSibling;
642                var expanded = btn.getAttribute("aria-expanded") === "true";
643                var nextExpanded = !expanded;
644                btn.setAttribute("aria-expanded", String(nextExpanded));
645                if (body) body.hidden = expanded;
646                if (!key) return;
647                var open = loadOpenCategories();
648                if (nextExpanded) {
649                    if (open.indexOf(key) === -1) open.push(key);
650                } else {
651                    open = open.filter(function (k) { return k !== key; });
652                }
653                saveOpenCategories(open);
654            });
655        });
656
657        document.addEventListener("keydown", function (e) {
658            if (e.key === "Escape" && overlay && !overlay.hidden) closeCookieModal();
659        });
660    }
661
662    function syncUI() {
663        document.querySelectorAll(".a11y-tile[data-mode]").forEach(function (btn) {
664            var on = state.mode === btn.dataset.mode;
665            btn.setAttribute("aria-pressed", String(on));
666            btn.classList.toggle("is-active", on);
667        });
668
669        document.querySelectorAll(".a11y-tile[data-toggle]").forEach(function (btn) {
670            var on = !!state[btn.dataset.toggle];
671            btn.setAttribute("aria-pressed", String(on));
672            btn.classList.toggle("is-active", on);
673        });
674
675        document.querySelectorAll(".a11y-slider-row").forEach(function (row) {
676            var key = row.dataset.key;
677            var value = state[key];
678            var input = row.querySelector(".a11y-slider");
679            var display = row.querySelector(".a11y-slider-row__value");
680            if (input) input.value = value;
681            if (display) display.textContent = value + "%";
682        });
683    }
684
685    function setSliderValue(key, value) {
686        state[key] = clampSlider(value);
687        updateState();
688    }
689
690    function init() {
691        loadConsentSettings();
692        state = loadA11y();
693        buildWidget();
694        wrapContentRoot();
695        initCookieUI();
696        apply();
697        syncUI();
698
699        var fab = document.getElementById("a11yFab");
700        var overlay = document.getElementById("a11yOverlay");
701        var closeBtn = document.getElementById("a11yClose");
702        var resetBtn = document.getElementById("a11yReset");
703
704        function open() {
705            overlay.hidden = false;
706            document.body.classList.add("a11y-modal-open");
707            closeBtn.focus();
708        }
709
710        function close() {
711            overlay.hidden = true;
712            document.body.classList.remove("a11y-modal-open");
713            fab.focus();
714        }
715
716        fab.addEventListener("click", open);
717        closeBtn.addEventListener("click", close);
718        resetBtn.addEventListener("click", resetAll);
719
720        overlay.addEventListener("click", function (e) {
721            if (e.target === overlay) close();
722        });
723
724        document.addEventListener("keydown", function (e) {
725            if (e.key === "Escape" && !overlay.hidden) close();
726        });
727
728        overlay.querySelectorAll(".a11y-tile[data-mode]").forEach(function (btn) {
729            btn.addEventListener("click", function () {
730                var mode = btn.dataset.mode;
731                state.mode = state.mode === mode ? null : mode;
732                updateState();
733            });
734        });
735
736        overlay.querySelectorAll(".a11y-tile[data-toggle]").forEach(function (btn) {
737            btn.addEventListener("click", function () {
738                var key = btn.dataset.toggle;
739                state[key] = !state[key];
740                updateState();
741            });
742        });
743
744        overlay.querySelectorAll(".a11y-slider-row").forEach(function (row) {
745            var key = row.dataset.key;
746            var input = row.querySelector(".a11y-slider");
747
748            input.addEventListener("input", function () {
749                setSliderValue(key, input.value);
750            });
751
752            row.querySelectorAll(".a11y-stepper").forEach(function (stepper) {
753                stepper.addEventListener("click", function () {
754                    var dir = parseInt(stepper.dataset.dir, 10);
755                    setSliderValue(key, state[key] + dir * SLIDER_STEP);
756                });
757            });
758        });
759    }
760
761    if (document.readyState === "loading") {
762        document.addEventListener("DOMContentLoaded", init);
763    } else {
764        init();
765    }
766})();

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.