Add GDPR cookie-consent banner (Consent Mode v2)

- consent.js: self-contained, non-blocking bottom banner in EN/MK/SI,
  remembers the choice in localStorage, links to the per-language cookie
  policy. Accept -> gtag consent 'update' granted; Decline stays denied.
- Head GA snippet restores a returning visitor's prior "granted" choice
  before the first hit; loads /consent.js on every page.
- Re-openable via window.msosOpenConsent() for a "manage cookies" link.

Verified in-browser: shows for new visitors, Accept grants + remembers,
reload keeps consent without re-prompting, Slovene text + /si/ policy link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
popovskikandClaude Opus 4.8 committed 2026-08-02 18:30:35 +02:00
1 parent 9a6d2c335c
commit f24fc704d6
203 files changed
+1100

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
/* MSOS cookie-consent banner (GDPR / Google Consent Mode v2).
* Non-blocking bottom card. Remembers the choice in localStorage. On "Accept"
* it grants analytics consent so GA4 (loaded denied-by-default in <head>) can
* set cookies; on "Decline" it stays denied. Trilingual, self-contained,
* no dependencies. Re-open anytime via window.msosOpenConsent(). */
(function () {
var KEY = 'msos-consent';
// Language from <html lang> (Slovene folder /si/ uses lang="sl").
var docLang = (document.documentElement.getAttribute('lang') || 'en').toLowerCase();
var lang = docLang === 'sl' ? 'si' : (['en', 'mk', 'si'].indexOf(docLang) === -1 ? 'en' : docLang);
var T = {
en: { text: 'We use cookies to measure how the site is used and improve it. You can accept or decline analytics cookies.',
accept: 'Accept', decline: 'Decline', policy: 'Cookie policy', aria: 'Cookie consent' },
mk: { text: 'Користиме колачиња за да измериме како се користи страницата и да ја подобриме. Можете да ги прифатите или одбиете аналитичките колачиња.',
accept: 'Прифати', decline: 'Одбиј', policy: 'Политика за колачиња', aria: 'Согласност за колачиња' },
si: { text: 'Uporabljamo piškotke, da izmerimo, kako se stran uporablja, in jo izboljšamo. Analitične piškotke lahko sprejmete ali zavrnete.',
accept: 'Sprejmi', decline: 'Zavrni', policy: 'Politika piškotkov', aria: 'Soglasje za piškotke' }
};
var t = T[lang];
var policyHref = '/' + lang + '/cookie-policy/';
function gtagSafe() { window.dataLayer = window.dataLayer || []; window.gtag = window.gtag || function () { window.dataLayer.push(arguments); }; }
function grant() {
gtagSafe();
window.gtag('consent', 'update', {
ad_storage: 'granted', ad_user_data: 'granted',
ad_personalization: 'granted', analytics_storage: 'granted'
});
}
function injectStyles() {
if (document.getElementById('msos-cc-style')) return;
var css = '' +
'.msos-cc{position:fixed;left:16px;right:16px;bottom:16px;z-index:9999;max-width:560px;margin:0 auto;' +
'background:#fff;border:1px solid #EAECF0;border-radius:14px;box-shadow:0 12px 32px rgba(16,24,40,.14);' +
"padding:18px 20px;font-family:'Inter',system-ui,sans-serif;animation:msosccin .25s ease-out}" +
'@keyframes msosccin{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}' +
'.msos-cc p{margin:0 0 14px;font-size:14px;line-height:21px;color:#475467}' +
'.msos-cc a{color:#2D738C;font-weight:600;text-decoration:none}.msos-cc a:hover{text-decoration:underline}' +
'.msos-cc-row{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}' +
'.msos-cc button{font-family:inherit;font-size:14px;font-weight:600;border-radius:8px;padding:9px 18px;' +
'cursor:pointer;border:1px solid transparent;transition:background .15s,color .15s,border-color .15s}' +
'.msos-cc-accept{background:#2D738C;color:#fff}.msos-cc-accept:hover{background:#255f74}' +
'.msos-cc-decline{background:#fff;border-color:#D0D5DD;color:#344054}.msos-cc-decline:hover{background:#F9FAFB}' +
'@media(max-width:520px){.msos-cc-row{justify-content:stretch}.msos-cc button{flex:1}}';
var s = document.createElement('style');
s.id = 'msos-cc-style';
s.textContent = css;
document.head.appendChild(s);
}
function close(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
function show() {
injectStyles();
var box = document.createElement('div');
box.className = 'msos-cc';
box.setAttribute('role', 'dialog');
box.setAttribute('aria-label', t.aria);
box.innerHTML =
'<p>' + t.text + ' <a href="' + policyHref + '">' + t.policy + '</a></p>' +
'<div class="msos-cc-row">' +
'<button type="button" class="msos-cc-decline">' + t.decline + '</button>' +
'<button type="button" class="msos-cc-accept">' + t.accept + '</button>' +
'</div>';
box.querySelector('.msos-cc-accept').addEventListener('click', function () {
try { localStorage.setItem(KEY, 'granted'); } catch (e) {}
grant();
close(box);
});
box.querySelector('.msos-cc-decline').addEventListener('click', function () {
try { localStorage.setItem(KEY, 'denied'); } catch (e) {}
close(box);
});
document.body.appendChild(box);
}
// Let a "manage cookies" link re-open the banner: <a onclick="msosOpenConsent()">
window.msosOpenConsent = function () { if (!document.querySelector('.msos-cc')) show(); };
var choice = null;
try { choice = localStorage.getItem(KEY); } catch (e) {}
if (choice !== 'granted' && choice !== 'denied') {
if (document.body) show();
else document.addEventListener('DOMContentLoaded', show);
}
})();