diff --git a/Website/main.py b/Website/main.py index 7bcd3ec..d145a78 100644 --- a/Website/main.py +++ b/Website/main.py @@ -82,7 +82,8 @@ def set_security_headers(response): response.headers["X-XSS-Protection"] = "1; mode=block" response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" - response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com; img-src 'self' data:; connect-src 'self'; frame-src 'self' https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com https://youtube-nocookie.com; media-src 'self' https:;" + # Fonts no longer loaded from Google Fonts; remove font hosts + response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-src 'self' https://www.youtube.com https://youtube.com https://www.youtube-nocookie.com https://youtube-nocookie.com; media-src 'self' https:;" return response BASE_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/Website/templates/admin_system.html b/Website/templates/admin_system.html index 4a6c885..365546a 100644 --- a/Website/templates/admin_system.html +++ b/Website/templates/admin_system.html @@ -232,7 +232,7 @@ td, button, .action-link, .inline-admin-form input { - font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace; + font-family: Arial, Helvetica, sans-serif; } .head h1 { @@ -385,7 +385,7 @@ button, background: #0b1114; color: var(--term-text); padding: 0.36rem 0.5rem; - font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace; + font-family: Arial, Helvetica, sans-serif; } .log-output { margin: 0; diff --git a/Website/templates/base.html b/Website/templates/base.html index 6785b23..321091a 100644 --- a/Website/templates/base.html +++ b/Website/templates/base.html @@ -24,9 +24,7 @@ {% block head %} - - - + + + @@ -769,27 +785,120 @@ var consentKey = "cookie_consent_v1"; - function hasConsent() { - return window.localStorage.getItem(consentKey) === "accepted" || document.cookie.indexOf("cookie_consent=accepted") !== -1; + function getConsent() { + try { + var raw = window.localStorage.getItem(consentKey); + if (!raw) return null; + return JSON.parse(raw); + } catch (e) { + return null; + } } - function setConsent() { - window.localStorage.setItem(consentKey, "accepted"); - document.cookie = "cookie_consent=accepted; Max-Age=" + (60 * 60 * 24 * 365) + "; Path=/; SameSite=Strict"; + 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 + } } - if (!hasConsent()) { - banner.classList.remove("hidden"); + function showBannerIfNeeded() { + var c = getConsent(); + if (!c) { + banner.classList.remove("hidden"); + } } + showBannerIfNeeded(); + acceptBtn.addEventListener("click", function () { - setConsent(); + var obj = { status: 'accepted', necessary: true, analytics: true, marketing: true, saved_at: Date.now() }; + saveConsent(obj); banner.classList.add("hidden"); }); - declineBtn.addEventListener("click", function () { - window.location.href = "https://www.ecosia.org"; + // 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'); + } + }); + } catch (e) { + console.error('applyConsent error', e); + } + } + + // Run once on load in case user already saved consent + applyConsent(); })(); diff --git a/Website/templates/main.html b/Website/templates/main.html index 67b6d5f..14e8991 100644 --- a/Website/templates/main.html +++ b/Website/templates/main.html @@ -717,7 +717,6 @@
- Lehrmittelverwaltungssoftware für Schulen

Die Software, die iPads genauso effizient verwaltet wie das Schulbuch.

Radikale Einfachheit für Ihren Schulalltag. Starten Sie in nur 5 Minuten mit der modernen Inventarsoftware für alle Schularten. Hybrid, intuitiv, sofort einsatzbereit. @@ -796,7 +795,7 @@

Zero Maintenance

-

Reine Web‑App: automatische Updates, keine lokalen Server, nahezu kein Betriebsaufwand für Ihre IT.

+

Reine Web‑App: automatische Updates, keine lokalen Server, nahezu kein Betriebsaufwand für Ihre IT. Deswegen benötigen sie keine App oder irgend etwas anderes als einen Internetzugang!

Datenschutz & DSGVO

diff --git a/Website/templates/my_tutorials.html b/Website/templates/my_tutorials.html index 181d428..a0e2586 100644 --- a/Website/templates/my_tutorials.html +++ b/Website/templates/my_tutorials.html @@ -72,7 +72,7 @@ font-size: 1.8rem; color: var(--text-main); margin-bottom: 0.5rem; - font-family: "Merriweather", Georgia, serif; + font-family: Arial, Helvetica, sans-serif; } .video-meta { @@ -188,7 +188,7 @@ margin-bottom: 0.5rem; font-weight: 600; line-height: 1.4; - font-family: "Merriweather", Georgia, serif; + font-family: Arial, Helvetica, sans-serif; } .tutorial-card-meta {