Files
Website/cookie-consent.js
Claude Code afe25fc728 Add DSGVO-compliant cookie consent banner and fix navbar consistency
- Implement custom cookie consent banner with opt-in gating pattern
- Add comprehensive privacy documentation (DSGVO/GDPR compliant)
- Integrate consent management into production (index.html)
- Add multilingual support for cookie settings (DE/EN)
- Fix navbar font-size inconsistency across legal pages
- Include mobile.css in datenschutz.html and impressum.html
- Add complete implementation documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 19:45:31 +00:00

528 Zeilen
20 KiB
JavaScript

/**
* Cookie Consent Manager - DSGVO-konform
* IntelSight / Aegis-Sight
* Version 1.0
*/
(function() {
'use strict';
// === CONFIGURATION ===
const CONFIG = {
CONSENT_VERSION: '1.0',
CONSENT_DURATION: 365, // days
STORAGE_KEY: 'insights-consent',
STORAGE_EXPIRES: 'insights-consent-expires',
STORAGE_DETAILS: 'insights-consent-details',
TRACKING_SCRIPT: '/insights/t.js',
SESSION_COOKIE: '_insights_session'
};
// === TRANSLATIONS ===
const TRANSLATIONS = {
de: {
title: 'Diese Website nutzt Cookies 🍪',
text: 'Wir verwenden ein selbst gehostetes Analyse-Tool, um unsere Website zu verbessern. Dabei erfassen wir anonymisierte Informationen über Ihre Nutzung (besuchte Seiten, Browser, ungefährer Standort). Alle Daten bleiben auf unserem Server in Deutschland und werden niemals an Dritte weitergegeben.',
privacy: 'Mit "Alle akzeptieren" stimmen Sie der Verwendung von Analyse-Cookies zu. Sie können Ihre Einwilligung jederzeit in den Cookie-Einstellungen widerrufen.',
btnAcceptAll: '✓ Alle akzeptieren',
btnRejectAll: '✗ Nur notwendige',
btnSettings: 'Details & Einstellungen',
settingsTitle: 'Cookie-Einstellungen',
categoryNecessary: 'Notwendig',
categoryAnalytics: 'Statistik & Analyse',
necessaryDesc: 'Technisch erforderliche Cookies für Login und Sicherheit. Diese Kategorie kann nicht deaktiviert werden.',
analyticsDesc: 'Anonymisierte Auswertung der Website-Nutzung zur Verbesserung unserer Inhalte. Alle Daten bleiben auf unserem Server in Deutschland.',
btnSaveSettings: 'Auswahl speichern',
linkPrivacy: 'Datenschutzerklärung',
linkImprint: 'Impressum',
gpcTitle: 'Global Privacy Control erkannt',
gpcText: 'Ihr Browser signalisiert, dass Sie nicht getrackt werden möchten (GPC). Wir respektieren diese Einstellung und haben Analyse-Cookies automatisch deaktiviert.'
},
en: {
title: 'This website uses cookies 🍪',
text: 'We use a self-hosted analytics tool to improve our website. We collect anonymized information about your usage (pages visited, browser, approximate location). All data remains on our server in Germany and is never shared with third parties.',
privacy: 'By clicking "Accept all", you consent to the use of analytics cookies. You can revoke your consent at any time in the cookie settings.',
btnAcceptAll: '✓ Accept all',
btnRejectAll: '✗ Only necessary',
btnSettings: 'Details & Settings',
settingsTitle: 'Cookie Settings',
categoryNecessary: 'Necessary',
categoryAnalytics: 'Statistics & Analytics',
necessaryDesc: 'Technically required cookies for login and security. This category cannot be disabled.',
analyticsDesc: 'Anonymized analysis of website usage to improve our content. All data remains on our server in Germany.',
btnSaveSettings: 'Save selection',
linkPrivacy: 'Privacy Policy',
linkImprint: 'Imprint',
gpcTitle: 'Global Privacy Control detected',
gpcText: 'Your browser signals that you do not want to be tracked (GPC). We respect this setting and have automatically disabled analytics cookies.'
}
};
// === STATE ===
let currentLanguage = document.documentElement.lang || 'de';
let consentState = {
necessary: true,
analytics: false
};
// === UTILITY FUNCTIONS ===
function getTranslation(key) {
return TRANSLATIONS[currentLanguage]?.[key] || TRANSLATIONS.de[key];
}
function setStorageWithExpiry(key, value, days) {
const now = new Date();
const item = {
value: value,
expires: now.getTime() + (days * 24 * 60 * 60 * 1000)
};
try {
localStorage.setItem(key, JSON.stringify(item));
} catch (e) {
console.warn('[CookieConsent] LocalStorage not available:', e);
}
}
function getStorageWithExpiry(key) {
try {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
const now = new Date();
if (now.getTime() > item.expires) {
localStorage.removeItem(key);
return null;
}
return item.value;
} catch (e) {
console.warn('[CookieConsent] Error reading from LocalStorage:', e);
return null;
}
}
function detectGPC() {
// Check for Global Privacy Control
if (navigator.globalPrivacyControl === true) {
return true;
}
// Check DNT as fallback (deprecated but still used)
if (navigator.doNotTrack === '1' || window.doNotTrack === '1') {
return true;
}
return false;
}
function saveConsentDetails() {
const details = {
timestamp: new Date().toISOString(),
version: CONFIG.CONSENT_VERSION,
categories: consentState,
language: currentLanguage,
userAgent: navigator.userAgent,
gpcSignal: detectGPC()
};
try {
localStorage.setItem(CONFIG.STORAGE_DETAILS, JSON.stringify(details));
} catch (e) {
console.warn('[CookieConsent] Could not save consent details:', e);
}
}
function deleteCookie(name) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
}
// === TRACKING CONTROL ===
function loadTracking() {
if (consentState.analytics) {
// Check if script already loaded
if (document.querySelector(`script[src="${CONFIG.TRACKING_SCRIPT}"]`)) {
console.log('[CookieConsent] Tracking script already loaded');
return;
}
const script = document.createElement('script');
script.src = CONFIG.TRACKING_SCRIPT;
script.async = true;
script.onerror = () => {
console.error('[CookieConsent] Failed to load tracking script');
};
document.head.appendChild(script);
console.log('[CookieConsent] Analytics enabled - tracking script loaded');
}
}
function disableTracking() {
// Remove tracking script if present
const trackingScript = document.querySelector(`script[src="${CONFIG.TRACKING_SCRIPT}"]`);
if (trackingScript) {
trackingScript.remove();
}
// Delete session cookie
deleteCookie(CONFIG.SESSION_COOKIE);
console.log('[CookieConsent] Analytics disabled - tracking blocked');
}
// === CONSENT MANAGEMENT ===
function saveConsent(analytics) {
consentState.analytics = analytics;
const consentValue = analytics ? 'accepted' : 'rejected';
setStorageWithExpiry(CONFIG.STORAGE_KEY, consentValue, CONFIG.CONSENT_DURATION);
saveConsentDetails();
if (analytics) {
loadTracking();
} else {
disableTracking();
}
console.log('[CookieConsent] Consent saved:', consentValue);
}
function loadConsent() {
const consent = getStorageWithExpiry(CONFIG.STORAGE_KEY);
if (consent === null) {
// Check for GPC - auto-reject if enabled
if (detectGPC()) {
console.log('[CookieConsent] GPC detected - auto-rejecting analytics');
consentState.analytics = false;
return null; // Still show banner with GPC notice
}
return null;
}
consentState.analytics = (consent === 'accepted');
// Check version
try {
const details = JSON.parse(localStorage.getItem(CONFIG.STORAGE_DETAILS) || '{}');
if (details.version !== CONFIG.CONSENT_VERSION) {
console.log('[CookieConsent] Version mismatch - re-prompting');
return null;
}
} catch (e) {
console.warn('[CookieConsent] Could not verify consent version');
}
return consent;
}
// === UI CREATION ===
function createBannerHTML() {
const gpcDetected = detectGPC();
return `
<div id="cookie-consent-backdrop" class="active"></div>
<div id="cookie-consent-banner" class="active" role="dialog" aria-labelledby="consent-title" aria-modal="true">
<div class="consent-content">
${gpcDetected ? `
<div class="gpc-notice">
<div class="gpc-notice-icon">🛡️</div>
<div class="gpc-notice-text">
<strong>${getTranslation('gpcTitle')}</strong>
${getTranslation('gpcText')}
</div>
</div>
` : ''}
<div class="consent-header">
<h2 id="consent-title">${getTranslation('title')}</h2>
</div>
<div class="consent-text">
<p>${getTranslation('text')}</p>
<p><small>${getTranslation('privacy')}</small></p>
</div>
<div class="consent-buttons">
<button class="consent-btn consent-btn-outline" id="btn-settings">
${getTranslation('btnSettings')}
</button>
<button class="consent-btn consent-btn-primary" id="btn-accept-all">
${getTranslation('btnAcceptAll')}
</button>
<button class="consent-btn consent-btn-secondary" id="btn-reject-all">
${getTranslation('btnRejectAll')}
</button>
</div>
</div>
</div>
`;
}
function createSettingsHTML() {
return `
<div id="cookie-consent-settings" role="dialog" aria-labelledby="settings-title" aria-modal="true">
<div class="settings-header">
<h3 id="settings-title">${getTranslation('settingsTitle')}</h3>
<button class="settings-close" aria-label="Close">&times;</button>
</div>
<div class="settings-content">
<!-- Necessary Category -->
<div class="cookie-category">
<div class="category-header">
<div class="category-title">
<span>${getTranslation('categoryNecessary')}</span>
<span class="badge badge-required">Immer aktiv</span>
</div>
<div class="category-toggle disabled active"></div>
</div>
<div class="category-description">
${getTranslation('necessaryDesc')}
</div>
<div class="category-details">
<strong>Cookies in dieser Kategorie:</strong>
<ul>
<li>Keine Cookies (nur im Login-Bereich)</li>
</ul>
</div>
</div>
<!-- Analytics Category -->
<div class="cookie-category">
<div class="category-header" id="analytics-category-header">
<div class="category-title">
<span>${getTranslation('categoryAnalytics')}</span>
</div>
<div class="category-toggle" id="analytics-toggle"
role="switch"
aria-checked="${consentState.analytics}"
tabindex="0"></div>
</div>
<div class="category-description">
${getTranslation('analyticsDesc')}
</div>
<div class="category-details">
<strong>IntelSight Analytics (Self-Hosted)</strong>
<ul>
<li><strong>Cookie:</strong> _insights_session (30 Minuten)</li>
<li><strong>Zweck:</strong> Session-Tracking, Bounce-Rate Berechnung</li>
<li><strong>Daten:</strong> Besuchte Seiten, Browser-Typ, ungefährer Standort (Stadt)</li>
<li><strong>Server:</strong> Deutschland (aegis-sight.de)</li>
<li><strong>Weitergabe:</strong> Keine Drittanbieter</li>
</ul>
</div>
</div>
</div>
<div class="settings-links">
<a href="datenschutz.html" target="_blank">${getTranslation('linkPrivacy')}</a>
<a href="impressum.html" target="_blank">${getTranslation('linkImprint')}</a>
</div>
<div class="settings-footer">
<button class="consent-btn consent-btn-secondary" id="btn-save-settings">
${getTranslation('btnSaveSettings')}
</button>
<button class="consent-btn consent-btn-primary" id="btn-accept-all-settings">
${getTranslation('btnAcceptAll')}
</button>
</div>
</div>
`;
}
// === UI CONTROL ===
function showBanner() {
// Check if already exists
if (document.getElementById('cookie-consent-banner')) {
const banner = document.getElementById('cookie-consent-banner');
const backdrop = document.getElementById('cookie-consent-backdrop');
banner.classList.add('active');
backdrop.classList.add('active');
return;
}
// Create and append
const container = document.createElement('div');
container.innerHTML = createBannerHTML();
document.body.appendChild(container.firstElementChild); // backdrop
document.body.appendChild(container.lastElementChild); // banner
// Add event listeners
document.getElementById('btn-accept-all').addEventListener('click', handleAcceptAll);
document.getElementById('btn-reject-all').addEventListener('click', handleRejectAll);
document.getElementById('btn-settings').addEventListener('click', showSettings);
// Prevent page scroll
document.body.style.overflow = 'hidden';
// Focus trap
document.getElementById('btn-accept-all').focus();
}
function hideBanner() {
const banner = document.getElementById('cookie-consent-banner');
const backdrop = document.getElementById('cookie-consent-backdrop');
if (banner) {
banner.classList.remove('active');
backdrop.classList.remove('active');
setTimeout(() => {
banner.remove();
backdrop.remove();
}, 400);
}
// Re-enable page scroll
document.body.style.overflow = '';
}
function showSettings() {
// Create settings modal if not exists
if (!document.getElementById('cookie-consent-settings')) {
const container = document.createElement('div');
container.innerHTML = createSettingsHTML();
document.body.appendChild(container.firstElementChild);
// Add event listeners
document.querySelector('.settings-close').addEventListener('click', hideSettings);
document.getElementById('btn-save-settings').addEventListener('click', handleSaveSettings);
document.getElementById('btn-accept-all-settings').addEventListener('click', handleAcceptAll);
// Analytics toggle
const analyticsToggle = document.getElementById('analytics-toggle');
const analyticsHeader = document.getElementById('analytics-category-header');
analyticsHeader.addEventListener('click', () => {
consentState.analytics = !consentState.analytics;
updateToggle();
});
analyticsToggle.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
consentState.analytics = !consentState.analytics;
updateToggle();
}
});
function updateToggle() {
analyticsToggle.classList.toggle('active', consentState.analytics);
analyticsToggle.setAttribute('aria-checked', consentState.analytics);
}
updateToggle();
}
const settings = document.getElementById('cookie-consent-settings');
settings.classList.add('active');
// Focus trap
document.querySelector('.settings-close').focus();
}
function hideSettings() {
const settings = document.getElementById('cookie-consent-settings');
if (settings) {
settings.classList.remove('active');
setTimeout(() => settings.remove(), 300);
}
}
// === EVENT HANDLERS ===
function handleAcceptAll() {
saveConsent(true);
hideBanner();
hideSettings();
}
function handleRejectAll() {
saveConsent(false);
hideBanner();
hideSettings();
}
function handleSaveSettings() {
saveConsent(consentState.analytics);
hideSettings();
hideBanner();
}
// === INITIALIZATION ===
function init() {
console.log('[CookieConsent] Initializing v' + CONFIG.CONSENT_VERSION);
// Load existing consent
const consent = loadConsent();
if (consent === null) {
// No consent yet - show banner
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', showBanner);
} else {
showBanner();
}
} else {
// Consent exists - apply settings
if (consentState.analytics) {
loadTracking();
}
console.log('[CookieConsent] Existing consent loaded:', consent);
}
// Cookie settings link in footer
document.addEventListener('DOMContentLoaded', () => {
const settingsLink = document.getElementById('cookie-settings-link');
if (settingsLink) {
settingsLink.addEventListener('click', (e) => {
e.preventDefault();
showBanner();
});
}
});
}
// === PUBLIC API ===
window.CookieConsent = {
show: showBanner,
hide: hideBanner,
showSettings: showSettings,
acceptAll: handleAcceptAll,
rejectAll: handleRejectAll,
getStatus: function() {
const consent = getStorageWithExpiry(CONFIG.STORAGE_KEY);
const details = JSON.parse(localStorage.getItem(CONFIG.STORAGE_DETAILS) || '{}');
return {
consent: consent,
analytics: consentState.analytics,
version: details.version,
timestamp: details.timestamp,
expires: localStorage.getItem(CONFIG.STORAGE_KEY) ?
JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEY)).expires : null,
gpc: detectGPC()
};
},
setLanguage: function(lang) {
if (TRANSLATIONS[lang]) {
currentLanguage = lang;
console.log('[CookieConsent] Language set to:', lang);
}
}
};
// Auto-initialize
init();
})();