feat: cookie banner compliance hardening — 5 legal requirements

1. Impressum link mandatory in banner (§5 TMG)
2. Pre-ticked prevention: only "required" categories pre-enabled (Planet49)
3. Cookie-Settings reopen link (§7(3) DSGVO — revocation as easy as consent)
4. Script-Blocking: data-cookie-category + type="text/plain" pattern
   Scripts only execute AFTER user consents to that category
5. Buttons already equal size (flex:1) — verified correct

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-02 15:50:54 +02:00
parent ca21feedc8
commit dccd9d09e5
@@ -245,13 +245,16 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str
const categoriesHTML = config.categories
.map((cat) => {
const isRequired = cat.isRequired
// COMPLIANCE: Only "required" categories may be pre-enabled (EuGH Planet49)
// Non-required categories must NEVER be defaultEnabled
const isEnabled = isRequired ? true : false
return `
<div class="cookie-banner-category" data-category="${cat.id}">
<div class="cookie-banner-category-info">
<div class="cookie-banner-category-name">${cat.name.de}</div>
<div class="cookie-banner-category-desc">${cat.description.de}</div>
</div>
<div class="cookie-banner-toggle ${cat.defaultEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
<div class="cookie-banner-toggle ${isEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
data-category="${cat.id}"
data-required="${isRequired}"></div>
</div>
@@ -286,10 +289,22 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str
</div>
</div>
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
${config.texts.privacyPolicyLink.de}
</a>
<div class="cookie-banner-links">
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
${config.texts.privacyPolicyLink.de}
</a>
<a href="${config.impressumUrl || '/impressum'}" class="cookie-banner-link" target="_blank">
Impressum
</a>
</div>
</div>
<!-- Cookie Settings Re-Open (§7(3) DSGVO — Widerruf so einfach wie Einwilligung) -->
<a href="#" id="cookieBannerReopen" class="cookie-settings-footer-link"
onclick="document.getElementById('cookieBanner').style.display='block';document.getElementById('cookieBannerOverlay').classList.add('active');return false;"
style="position:fixed;bottom:8px;left:8px;z-index:9990;font-size:11px;color:#6b7280;text-decoration:none;background:rgba(255,255,255,0.9);padding:4px 8px;border-radius:4px;border:1px solid #e5e7eb;">
Cookie-Einstellungen
</a>
`.trim()
}
@@ -397,6 +412,31 @@ function generateJS(config: CookieBannerConfig): string {
overlay?.classList.remove('active');
}
// Script-Blocking: activate scripts with data-cookie-category ONLY after consent
function activateConsentedScripts() {
const consent = getConsent();
if (!consent) return;
// Find all blocked scripts (type="text/plain" with data-cookie-category)
document.querySelectorAll('script[data-cookie-category][type="text/plain"]').forEach(script => {
const category = script.getAttribute('data-cookie-category');
if (consent[category] === true) {
// Replace type to activate the script
const newScript = document.createElement('script');
if (script.src) newScript.src = script.src;
else newScript.textContent = script.textContent;
newScript.type = 'text/javascript';
script.parentNode.replaceChild(newScript, script);
}
});
// Also fire custom event for programmatic listeners
window.dispatchEvent(new CustomEvent('cookieConsentActivated', { detail: consent }));
}
// Run script activation after consent is saved
window.addEventListener('cookieConsentUpdated', activateConsentedScripts);
window.CookieConsent = {
getConsent,
saveConsent,
@@ -405,14 +445,32 @@ function generateJS(config: CookieBannerConfig): string {
document.getElementById('cookieBanner')?.classList.add('active');
document.getElementById('cookieBannerOverlay')?.classList.add('active');
},
hide: closeBanner
hide: closeBanner,
activateScripts: activateConsentedScripts,
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBanner);
document.addEventListener('DOMContentLoaded', () => {
initBanner();
activateConsentedScripts();
});
} else {
initBanner();
activateConsentedScripts();
}
})();
/*
* USAGE: Script-Blocking
*
* Instead of:
* <script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
*
* Use:
* <script type="text/plain" data-cookie-category="statistics"
* src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>
*
* The script will only execute AFTER the user consents to "statistics".
*/
`.trim()
}