diff --git a/Website/templates/base.html b/Website/templates/base.html index e226149..4405c99 100644 --- a/Website/templates/base.html +++ b/Website/templates/base.html @@ -754,6 +754,110 @@ (function () { var menuToggle = document.getElementById('mobileMenuToggle'); var navLinks = document.getElementById('mainNav'); + var consentKey = 'cookie_consent_v1'; + var banner = document.getElementById('cookieBanner'); + var acceptBtn = document.getElementById('cookieAcceptBtn'); + var prefsBtn = document.getElementById('cookiePrefsBtn'); + var prefsModal = document.getElementById('cookiePrefsModal'); + var analyticsCheckbox = document.getElementById('cookie_analytics'); + var marketingCheckbox = document.getElementById('cookie_marketing'); + var prefsSave = document.getElementById('cookiePrefsSave'); + var prefsAcceptAll = document.getElementById('cookiePrefsAcceptAll'); + var prefsOnlyNecessary = document.getElementById('cookiePrefsOnlyNecessary'); + + function getConsent() { + try { + var raw = window.localStorage.getItem(consentKey); + return raw ? JSON.parse(raw) : null; + } catch (e) { + return null; + } + } + + function applyConsent() { + try { + var c = getConsent() || {}; + var analyticsAllowed = !!c.analytics; + var marketingAllowed = !!c.marketing; + + document.querySelectorAll('script[type="text/plain"]').forEach(function (node) { + var required = node.getAttribute('data-consent') || 'analytics'; + var allowed = (required === 'analytics' && analyticsAllowed) || (required === 'marketing' && marketingAllowed) || required === 'necessary'; + if (!allowed) return; + + if (node.getAttribute('data-consent-applied') === '1') return; + + var script = document.createElement('script'); + if (node.src) script.src = node.src; + else script.text = node.textContent || node.innerText || ''; + + var type = node.getAttribute('data-script-type'); + if (type) script.type = type; + + node.parentNode.insertBefore(script, node); + node.setAttribute('data-consent-applied', '1'); + }); + } catch (e) { + console.error('applyConsent error', e); + } + } + + function saveConsent(obj) { + try { + window.localStorage.setItem(consentKey, JSON.stringify(obj)); + document.cookie = 'cookie_consent=' + encodeURIComponent(JSON.stringify(obj)) + '; Max-Age=' + (60 * 60 * 24 * 365) + '; Path=/; SameSite=Strict'; + applyConsent(); + } catch (e) { + console.error('saveConsent error', e); + } + } + + function showBannerIfNeeded() { + if (banner && !getConsent()) banner.classList.remove('hidden'); + } + + function openPrefs() { + var consent = getConsent() || {}; + if (analyticsCheckbox) analyticsCheckbox.checked = !!consent.analytics; + if (marketingCheckbox) marketingCheckbox.checked = !!consent.marketing; + if (prefsModal) prefsModal.classList.remove('hidden'); + } + + function closePrefs() { + if (prefsModal) prefsModal.classList.add('hidden'); + if (banner) banner.classList.add('hidden'); + } + + window.printDocument = function () { + try { window.print(); } catch (e) { console.error('print error', e); } + }; + + window.printPdf = function (url) { + try { + var popup = window.open(url, '_blank'); + if (!popup) return; + var attempts = 0; + var timer = setInterval(function () { + attempts += 1; + try { + if (popup.document && (popup.document.readyState === 'complete' || popup.document.readyState === 'interactive')) { + popup.focus(); + popup.print(); + clearInterval(timer); + } + } catch (err) { + if (attempts > 8) { + clearInterval(timer); + try { popup.focus(); } catch (focusError) {} + } + } + if (attempts > 30) clearInterval(timer); + }, 300); + } catch (e) { + console.error('printPdf error', e); + } + }; + if (menuToggle && navLinks) { menuToggle.addEventListener('click', function () { var isExpanded = this.getAttribute('aria-expanded') === 'true'; @@ -764,11 +868,10 @@ navLinks.querySelectorAll('a').forEach(function (link) { link.addEventListener('click', function () { - if (navLinks.classList.contains('open')) { - menuToggle.setAttribute('aria-expanded', 'false'); - navLinks.classList.remove('open'); - navLinks.setAttribute('aria-hidden', 'true'); - } + if (!navLinks.classList.contains('open')) return; + menuToggle.setAttribute('aria-expanded', 'false'); + navLinks.classList.remove('open'); + navLinks.setAttribute('aria-hidden', 'true'); }); }); @@ -780,16 +883,6 @@ } }); - navLinks.querySelectorAll('a').forEach(function (link) { - link.addEventListener('click', function () { - if (navLinks.classList.contains('open')) { - menuToggle.setAttribute('aria-expanded', 'false'); - navLinks.classList.remove('open'); - navLinks.setAttribute('aria-hidden', 'true'); - } - }); - }); - window.addEventListener('resize', function () { if (window.innerWidth > 820 && navLinks.classList.contains('open')) { menuToggle.setAttribute('aria-expanded', 'false'); @@ -799,161 +892,39 @@ }); } - var banner = document.getElementById("cookieBanner"); - var acceptBtn = document.getElementById("cookieAcceptBtn"); - var declineBtn = document.getElementById("cookieDeclineBtn"); - if (!banner || !acceptBtn || !declineBtn) { - return; - } + if (banner && acceptBtn) { + showBannerIfNeeded(); - var consentKey = "cookie_consent_v1"; + acceptBtn.addEventListener('click', function () { + saveConsent({ status: 'accepted', necessary: true, analytics: true, marketing: true, saved_at: Date.now() }); + banner.classList.add('hidden'); + }); - function getConsent() { - try { - var raw = window.localStorage.getItem(consentKey); - if (!raw) return null; - return JSON.parse(raw); - } catch (e) { - return null; - } - } + if (prefsBtn) prefsBtn.addEventListener('click', openPrefs); - function saveConsent(obj) { - try { - window.localStorage.setItem(consentKey, JSON.stringify(obj)); - var cookieVal = obj.status || (obj.accepted ? 'accepted' : 'custom'); - document.cookie = "cookie_consent=" + encodeURIComponent(JSON.stringify(obj)) + "; Max-Age=" + (60 * 60 * 24 * 365) + "; Path=/; SameSite=Strict"; - // Apply consent immediately: enable any scripts gated by consent - applyConsent(); - } catch (e) { - // ignore - } - } - - function showBannerIfNeeded() { - var c = getConsent(); - if (!c) { - banner.classList.remove("hidden"); - } - } - - showBannerIfNeeded(); - - acceptBtn.addEventListener("click", function () { - var obj = { status: 'accepted', necessary: true, analytics: true, marketing: true, saved_at: Date.now() }; - saveConsent(obj); - banner.classList.add("hidden"); - }); - - // Preferences modal handling - var prefsBtn = document.getElementById('cookiePrefsBtn'); - var prefsModal = document.getElementById('cookiePrefsModal'); - var analyticsCheckbox = document.getElementById('cookie_analytics'); - var marketingCheckbox = document.getElementById('cookie_marketing'); - var prefsSave = document.getElementById('cookiePrefsSave'); - var prefsAcceptAll = document.getElementById('cookiePrefsAcceptAll'); - var prefsOnlyNecessary = document.getElementById('cookiePrefsOnlyNecessary'); - - function openPrefs() { - // initialize from existing consent if present - var c = getConsent() || {}; - analyticsCheckbox.checked = !!c.analytics; - marketingCheckbox.checked = !!c.marketing; - prefsModal.classList.remove('hidden'); - } - - function closePrefs() { - prefsModal.classList.add('hidden'); - banner.classList.add('hidden'); - } - - if (prefsBtn) prefsBtn.addEventListener('click', openPrefs); - - prefsSave.addEventListener('click', function () { - var obj = { status: 'custom', necessary: true, analytics: !!analyticsCheckbox.checked, marketing: !!marketingCheckbox.checked, saved_at: Date.now() }; - saveConsent(obj); - closePrefs(); - }); - - prefsAcceptAll.addEventListener('click', function () { - var obj = { status: 'accepted', necessary: true, analytics: true, marketing: true, saved_at: Date.now() }; - saveConsent(obj); - closePrefs(); - }); - - prefsOnlyNecessary.addEventListener('click', function () { - var obj = { status: 'necessary', necessary: true, analytics: false, marketing: false, saved_at: Date.now() }; - saveConsent(obj); - closePrefs(); - }); - - // --- Consent-based script loader ------------------------------------------------- - // Usage: include scripts that require consent like this in templates: - // - // or for inline scripts: - // - function applyConsent() { - try { - var c = getConsent() || {}; - var analyticsAllowed = !!c.analytics; - var marketingAllowed = !!c.marketing; - - // Find all scripts that were intentionally disabled by using type="text/plain" - document.querySelectorAll('script[type="text/plain"]').forEach(function (node) { - var required = node.getAttribute('data-consent') || 'analytics'; - if ((required === 'analytics' && analyticsAllowed) || (required === 'marketing' && marketingAllowed) || required === 'necessary') { - var s = document.createElement('script'); - // copy src or inline content - if (node.src) s.src = node.src; - else s.text = node.textContent || node.innerText || ''; - // copy attributes that are safe/needed - var type = node.getAttribute('data-script-type'); - if (type) s.type = type; - // append to same parent - node.parentNode.insertBefore(s, node); - // mark original as applied to avoid double-inserting - node.setAttribute('data-consent-applied', '1'); - } + if (prefsSave) prefsSave.addEventListener('click', function () { + saveConsent({ + status: 'custom', + necessary: true, + analytics: !!(analyticsCheckbox && analyticsCheckbox.checked), + marketing: !!(marketingCheckbox && marketingCheckbox.checked), + saved_at: Date.now() }); - } catch (e) { - console.error('applyConsent error', e); - } + closePrefs(); + }); + + if (prefsAcceptAll) prefsAcceptAll.addEventListener('click', function () { + saveConsent({ status: 'accepted', necessary: true, analytics: true, marketing: true, saved_at: Date.now() }); + closePrefs(); + }); + + if (prefsOnlyNecessary) prefsOnlyNecessary.addEventListener('click', function () { + saveConsent({ status: 'necessary', necessary: true, analytics: false, marketing: false, saved_at: Date.now() }); + closePrefs(); + }); } - // Run once on load in case user already saved consent applyConsent(); - // Global print helpers - window.printDocument = function () { - try { window.print(); } catch (e) { console.error('print error', e); } - }; - - window.printPdf = function (url) { - try { - var w = window.open(url, '_blank'); - if (!w) return; - // Attempt to print after load; poll for readyState - var attempts = 0; - var t = setInterval(function () { - attempts++; - try { - if (w.document && (w.document.readyState === 'complete' || w.document.readyState === 'interactive')) { - w.focus(); - w.print(); - clearInterval(t); - } - } catch (err) { - // cross-domain or plugin viewer may block access; fallback: focus window and leave to user - if (attempts > 8) { - clearInterval(t); - try { w.focus(); } catch (e) {} - } - } - if (attempts > 30) clearInterval(t); - }, 300); - } catch (e) { - console.error('printPdf error', e); - } - }; })();